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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linters.yml +4 -2
  3. data/.rubocop.yml +1 -1
  4. data/Gemfile.lock +4 -2
  5. data/app/assets/javascripts/cm_admin/exports.js +48 -29
  6. data/app/assets/stylesheets/cm_admin/base/form.scss +7 -8
  7. data/app/assets/stylesheets/cm_admin/base/navbar.scss +9 -25
  8. data/app/assets/stylesheets/cm_admin/base/show.scss +1 -1
  9. data/app/assets/stylesheets/cm_admin/base/table.scss +1 -1
  10. data/app/assets/stylesheets/cm_admin/components/_buttons.scss +4 -0
  11. data/app/controllers/cm_admin/application_controller.rb +0 -8
  12. data/app/controllers/cm_admin/exports_controller.rb +14 -3
  13. data/app/controllers/cm_admin/resource_controller.rb +6 -8
  14. data/app/jobs/generate_export_file_job.rb +15 -0
  15. data/app/mailers/export_mailer.rb +22 -0
  16. data/app/models/concerns/exportable.rb +97 -0
  17. data/app/models/file_export.rb +17 -0
  18. data/app/views/cm_admin/main/_associated_table.html.slim +2 -0
  19. data/app/views/cm_admin/main/_card.html.slim +1 -1
  20. data/app/views/cm_admin/main/_kanban.html.slim +1 -1
  21. data/app/views/cm_admin/main/_table.html.slim +1 -1
  22. data/app/views/cm_admin/main/_top_navbar.html.slim +0 -10
  23. data/app/views/cm_admin/main/associated_index.html.slim +2 -4
  24. data/app/views/cm_admin/main/index.html.slim +7 -3
  25. data/app/views/export_mailer/export_email.html.slim +19 -0
  26. data/app/views/layouts/cm_admin.html.slim +12 -5
  27. data/app/views/layouts/mailer.html.slim +6 -0
  28. data/cm_admin.gemspec +5 -3
  29. data/config/routes.rb +1 -1
  30. data/lib/cm_admin/models/export.rb +2 -55
  31. data/lib/cm_admin/version.rb +1 -1
  32. data/lib/cm_admin/view_helpers.rb +55 -37
  33. data/lib/generators/cm_admin/install_generator.rb +1 -0
  34. metadata +26 -6
  35. 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: '08cb28438df1f6c84b31f95bf13779e18a2900e4973fcd7591744284ba791b93'
4
- data.tar.gz: 68ecf1bb6702de4a04486dd65213dd997828be32506cf9479322baf853c79fa0
3
+ metadata.gz: 213f8a5059f6d3687a1e8c751deeec3ffd7c523206116f93927ed0ce0e98d888
4
+ data.tar.gz: d6845b50243ba50c3dcd3ac8dcab2c66cc108628bf9bb499e1dd8912e3ac161c
5
5
  SHA512:
6
- metadata.gz: a5fbf72b4cbef166f981864eba01a34477f72b53171b6745efdf360dacf8b5f3f5e19a4b0d57b31f5eb27d9c821bbcbc5dc750a47468528b293792d8ea435e43
7
- data.tar.gz: cc020e41b2cd42e924bb4d2fb9410d30aab5fbcb95baf4366c48ae0ab0e64781584a901e1b83d578a4f726a8e03b7681763be8ebd5d26815d903f9d5782fe8a0
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
- reporter: github-pr-review # Change reporter.
27
- stylelint_input: '**/*.scss'
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.1.2)
4
+ cm-admin (4.2.0)
5
5
  caxlsx_rails
6
6
  cocoon (~> 1.2.15)
7
- csv-importer (~> 0.8.2)
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('click', '.export-to-file-btn', function(e) {
2
- e.preventDefault();
3
- var query_param = window.location.href.split("?")[1]
4
- var action = $('#export-to-file-form').get(0).getAttribute('action')
5
- $('#export-to-file-form').get(0).setAttribute('action', action + '?' + query_param);
6
- $("#export-to-file-form").submit();
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
- "click",
11
- '[data-behaviour="export-select-all"]',
12
- function (e) {
13
- if($(this).is(':checked')){
14
- $('[data-behaviour="export-checkbox"]').prop('checked', true)
15
-
16
- } else {
17
- $('[data-behaviour="export-checkbox"]').prop('checked', false)
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
- "click",
24
- '[data-behaviour="export-checkbox"]',
25
- function (e) {
26
- const container = $(this).closest('.row');
27
- if (container.find('[data-behaviour="export-checkbox"]:checked').length == container.find('[data-behaviour="export-checkbox"]').length) {
28
- $('[data-behaviour="export-select-all"]').prop('checked', true);
29
- } else {
30
- $('[data-behaviour="export-select-all"]').prop('checked', false);
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
- &.required-label::after {
54
- content: " *";
55
- color: $red;
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
- &__actions {
12
- @extend .d-flex.align-items-start;
13
- .export-container {
14
- position: relative;
15
- .export-popup {
16
- top: 45px;
17
- width: 156px;
18
- padding: 8px 0;
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
  }
@@ -52,7 +52,7 @@
52
52
  }
53
53
  }
54
54
  }
55
- .filters-bar {
55
+ .cm-index-page__filters {
56
56
  background-color: $grey-lightest-clr;
57
57
  }
58
58
  }
@@ -16,7 +16,7 @@
16
16
  }
17
17
 
18
18
  &__filters {
19
- // If you want to overwrite filters styles ---> add styles here
19
+ @include flex(row, space-between, center);
20
20
  }
21
21
 
22
22
  &__table-container {
@@ -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
- def export
10
- file_path = CmAdmin::Models::Export.generate_excel(params[:class_name], params, helpers)
11
- send_file file_path, disposition: 'attachment'
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
- = column_pop_up(@model)
58
+ = export_modal(@model)
59
59
  = manage_column_pop_up(@model)
@@ -13,5 +13,5 @@
13
13
  .btn.btn-primary.kanban-show-more data-page=1 Show more
14
14
  = render partial: 'cm_admin/main/show_as_drawer'
15
15
 
16
- = column_pop_up(@model)
16
+ = export_modal(@model)
17
17
  = 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
- = column_pop_up(@model)
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,5 +1,3 @@
1
1
  .cm-index-page.associated-index
2
- .cm-index-page__table-container
3
- == render partial: 'cm_admin/main/associated_table'
4
-
5
- // = column_pop_up(@associated_model)
2
+ .cm-index-page__table-container
3
+ == render partial: 'cm_admin/main/associated_table'
@@ -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
- .filters-bar
59
- == render partial: 'cm_admin/main/filters', locals: { filters: @associated_model.filters }
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'
@@ -0,0 +1,6 @@
1
+ doctype html
2
+ html
3
+ head
4
+ meta content="text/html; charset=UTF-8" http-equiv="Content-Type"
5
+ body
6
+ == yield
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 = '>= 2.7.0'
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', '~> 0.8.2'
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
@@ -5,7 +5,7 @@ CmAdmin::Engine.routes.draw do
5
5
  end
6
6
 
7
7
  controller 'exports' do
8
- post '/export_to_file', action: 'export'
8
+ post '/send_export_email', action: 'send_export_email'
9
9
  end
10
10
 
11
11
  # Defining action routes for each model
@@ -2,62 +2,9 @@ module CmAdmin
2
2
  module Models
3
3
  class Export
4
4
  class << self
5
- def generate_excel(klass_name, params, helpers)
6
- klass = klass_name.constantize
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
@@ -1,3 +1,3 @@
1
1
  module CmAdmin
2
- VERSION = '4.1.2'
2
+ VERSION = '4.2.0'
3
3
  end
@@ -15,66 +15,84 @@ module CmAdmin
15
15
  include ActionView::Helpers::TagHelper
16
16
  include ActionView::Helpers::NumberHelper
17
17
 
18
- def exportable(_klass, html_class: [])
19
- tag.a 'Export as excel', class: html_class.append('filter-btn modal-btn me-2'), data: { toggle: 'modal', target: '#exportmodal' } do
20
- concat tag.i class: 'fa fa-download'
21
- concat tag.span ' Export'
22
- end
23
- end
24
-
25
- def column_pop_up(klass, required_filters = nil)
26
- tag.div class: 'modal fade form-modal', id: 'exportmodal', role: 'dialog', aria: { labelledby: 'exportModal' } do
27
- tag.div class: 'modal-dialog modal-lg', role: 'document' do
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 pop_ups(klass, required_filters)
36
- tag.div do
37
- concat pop_up_header
38
- concat pop_up_body(klass, required_filters)
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 pop_up_header
39
+ def modal_header
43
40
  tag.div class: 'modal-header' do
44
- tag.button type: 'button', class: 'close', data: { dismiss: 'modal' }, aria: { label: 'Close' } do
45
- tag.span 'X', aria: { hidden: 'true' }
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 pop_up_body(klass, _required_filters)
52
- tag.div class: 'modal-body' do
53
- form_tag cm_admin.send('export_to_file_path'), id: 'export-to-file-form', style: 'width: 100%;', class: 'cm-admin-csv-export-form' do
54
- concat(content_tag(:div, class: 'column export-select-container') do
55
- concat check_box_tag('select_all', '1', false, data: { behaviour: 'export-select-all' })
56
- concat 'All'
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 checkbox_row(klass)
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 class: 'col-md-4' do
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.1.2
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-03 00:00:00.000000000 Z
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: 2.7.0
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