cm-admin 4.0.0 → 4.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 758deeaf34976fadccc5e68a438f0ba2cc3fe7f519431ffe750c740c162b92c7
4
- data.tar.gz: 770661bd71b01b11ec2f50c4a88adf8a182d6d082e0b8415ef099df8559e8279
3
+ metadata.gz: 3bf623e4d9823428bdd3094de10b24c89415824291ad8366aca4d79e4bd34db1
4
+ data.tar.gz: 4dc0b0fb0646b99ae721ceb0f23652b730d4e479aa44aae9fc720fd0d199d89a
5
5
  SHA512:
6
- metadata.gz: e87eb00d34c7c2e601078d815d6137ef4eddb22d543294d33301a102b3f9ff2931f46e353a832db435441df81cb32d198d06824bd8cef82984440fc0ef5186d5
7
- data.tar.gz: 594fcf9d069ef492dfb469e22cb4b609ea3b6d4c6acbf1404080c3d9e8b18ed2fa416ef2122a7035c9a53dc8dc915ae6724a768e1aa74fec16bd5735c9879cef
6
+ metadata.gz: ec0c5593c3b28ce5857c9a7ef1873ddd279b3486a3f9562a3c1975c4f22d4ca644557303cd292e6086c2a31e42acca337734f80001698b8087702ed56ec0fe5b
7
+ data.tar.gz: c02bbac2b9e9e8ad80c0d75b1249bb45065b830a0ddfb37433c09122292fcbbb43fa94d2d2ce2ce59d3d140947d8ca6a878fa2dd22acca9ccbe7d687beba14c1
@@ -0,0 +1,46 @@
1
+ name: Tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ test:
7
+ if: "!contains(github.event.head_commit.message, '[skip-tests]')"
8
+ runs-on: ubuntu-latest
9
+
10
+ services:
11
+ postgres:
12
+ image: postgres:17
13
+ env:
14
+ POSTGRES_USER: postgres
15
+ POSTGRES_PASSWORD: postgres
16
+ ports:
17
+ - 5432:5432
18
+
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v3
22
+
23
+ - name: Set up Ruby
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: 3.3
27
+ bundler-cache: true
28
+
29
+ - name: Install dependencies
30
+ working-directory: feature-app
31
+ run: bundle install
32
+
33
+ - name: Set up database
34
+ working-directory: feature-app
35
+ run: |
36
+ cp config/database.yml.ci config/database.yml
37
+ RAILS_ENV=test bundle exec rails db:create
38
+ RAILS_ENV=test bundle exec rails db:migrate
39
+
40
+ - name: Precompile assets
41
+ working-directory: feature-app
42
+ run: RAILS_ENV=test bundle exec rails assets:precompile
43
+
44
+ - name: Run RSpec tests
45
+ working-directory: feature-app
46
+ run: bundle exec rspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cm-admin (4.0.0)
4
+ cm-admin (4.1.1)
5
5
  caxlsx_rails
6
6
  cocoon (~> 1.2.15)
7
7
  csv-importer (~> 0.8.2)
@@ -58,4 +58,9 @@ $(document).on("click", '[data-behaviour="discard_form"]', function (e) {
58
58
  const form = $(this).closest("form");
59
59
  form[0].reset();
60
60
  window.history.back();
61
+ });
62
+
63
+ $(document).on("turbo:submit-end", function(e){
64
+ $('[data-behaviour="form_submit_spinner"]').addClass("visually-hidden");
65
+ return $('[data-behaviour="form_submit"]').removeClass("visually-hidden");
61
66
  });
@@ -6,12 +6,19 @@ module CmAdmin
6
6
  helper CmAdmin::ViewHelpers
7
7
 
8
8
  skip_before_action :verify_authenticity_token, only: :reset_sort_columns
9
+ before_action :set_current_user_permission
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
9
15
 
10
16
  def cm_index(params)
11
17
  @current_action = CmAdmin::Models::Action.find_by(@model, name: 'index')
12
18
  # Based on the params the filter and pagination object to be set
13
19
  authorize @ar_object, policy_class: "CmAdmin::#{controller_name.classify}Policy".constantize if defined? "CmAdmin::#{controller_name.classify}Policy".constantize
14
20
  records = "CmAdmin::#{@model.name}Policy::IndexScope".constantize.new(Current.user, @model.name.constantize).resolve
21
+ records = records.includes(@current_action.eager_load_associations) if @current_action.eager_load_associations.present?
15
22
  records = apply_scopes(records)
16
23
  @ar_object = if %w[table card].include?(params[:view_type]) || %i[table card].include?(@current_action.view_type)
17
24
  filter_by(params, records, filter_params: @model.filter_params(params))
@@ -163,6 +170,7 @@ module CmAdmin
163
170
  @current_action = @action
164
171
  if @action.parent == 'index'
165
172
  records = apply_scopes(records)
173
+ records = records.includes(@model.eager_load_associations) if @model.eager_load_associations.present?
166
174
  @ar_object = filter_by(params, records, filter_params: @model.filter_params(params))
167
175
  else
168
176
  resource_identifier
@@ -304,6 +312,7 @@ module CmAdmin
304
312
  return @ar_object unless @current_action.child_records
305
313
 
306
314
  child_records = @ar_object.send(@current_action.child_records)
315
+ child_records = child_records.includes(@current_action.eager_load_associations) if @current_action.eager_load_associations.present?
307
316
  child_records = apply_scopes(child_records)
308
317
  @reflection = @model.ar_model.reflect_on_association(@current_action.child_records)
309
318
  @associated_model = if @reflection.klass.column_names.include?('type')
@@ -0,0 +1,14 @@
1
+ module CmAdmin
2
+ module PermissionHelper
3
+
4
+ # Used in mode.rb while creating policies.
5
+ def has_access_to?(ar_model, action)
6
+ find_permission_by(ar_model, action).present?
7
+ end
8
+
9
+ # Checks if the current user permission has access to the specified model and action
10
+ def find_permission_by(ar_model, action)
11
+ CmCurrent.user_permissions.find { |permission| permission.ar_model_name == ar_model.name && permission.action_name == action.name }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ class CmCurrent < ActiveSupport::CurrentAttributes
2
+ attribute :user_permissions
3
+ end
@@ -19,4 +19,5 @@ class CmPermission < ApplicationRecord
19
19
  CmPermission.where(action_name: 'update', ar_model_name:, cm_role_id:).first&.destroy
20
20
  end
21
21
  end
22
+
22
23
  end
@@ -59,6 +59,6 @@
59
59
  == render partial: 'cm_admin/main/actions_dropdown', locals: { cm_model: @associated_model, ar_object: ar_object }
60
60
 
61
61
  .pagination-bar
62
- p.count-text.m-0 Showing #{@associated_ar_object.pagy.from} to #{@associated_ar_object.pagy.to} out of #{@associated_ar_object.pagy.count}
62
+ 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)}
63
63
  == render partial: 'cm_admin/main/cm_pagy_nav', locals: { pagy: @associated_ar_object.pagy }
64
64
  / = render partial: 'cm_admin/main/member_custom_action_modal', locals: { cm_model: @associated_model, ar_collection: @associated_ar_object }
@@ -9,10 +9,10 @@ nav.pagination-actions role="navigation" aria-label="pager"
9
9
 
10
10
  - pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
11
11
  - if item.is_a?(Integer) # page link
12
- span ==> link.call(item, item, 'class="btn-ghost"')
12
+ span ==> link.call(item, number_with_delimiter(item.to_i), 'class="btn-ghost"')
13
13
 
14
14
  - elsif item.is_a?(String) # current page
15
- span ==> link.call(item, item, 'class="btn-ghost active"')
15
+ span ==> link.call(item, number_with_delimiter(item.to_i), 'class="btn-ghost active"')
16
16
 
17
17
  - elsif item == :gap # page gap
18
18
  span.page.gap ==> pagy_t('pagy.nav.gap')
@@ -50,7 +50,7 @@
50
50
  - if @model
51
51
  == render partial: 'cm_admin/main/actions_dropdown', locals: { cm_model: @model, ar_object: ar_object }
52
52
  .pagination-bar
53
- p.count-text.m-0 Showing #{@ar_object.pagy.from} to #{@ar_object.pagy.to} out of #{@ar_object.pagy.count}
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
  = column_pop_up(@model)
@@ -35,5 +35,8 @@
35
35
  .cm-select-tag class="#{permission.present? ? '' : 'hidden'}"
36
36
  = select_tag(:policy_scope_name, options_for_select(model.policy_scopes.map{|policy_hash| [policy_hash[:display_name], policy_hash[:scope_name]]}, permission&.scope_name), {name: "role_permission[#{model.name}][#{action.name}][scope_name]", class: 'select-2', id: "policy-scope-#{model.name}-#{action.name}"})
37
37
  .form-submit-button-container
38
- = f.submit 'Save Changes', class: "btn-cta"
39
- = link_to 'Discard', cm_admin.send('cm_index_cm_role_path'), class: "btn-secondary ml-10"
38
+ = f.submit 'Save Changes', class: 'btn-cta', data: { behaviour: 'form_submit', form_class: "new_cm_permission", turbo_submits_with: 'Submitting...' }
39
+ = button_tag(type: 'button', class: 'btn-loading visually-hidden', data: { behaviour: 'form_submit_spinner' }) do
40
+ = content_tag(:span, '', class: 'spinner-border spinner-border-sm', role: 'status')
41
+ | Submitting...
42
+ = link_to 'Discard', cm_admin.send('cm_index_cm_role_path'), class: "btn-secondary ml-10"
data/cm_admin.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  # Specify which files should be added to the gem when it is released.
24
24
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
25
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|feature-app)/}) }
27
27
  end
28
28
  spec.bindir = 'exe'
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -188,12 +188,14 @@ module CmAdmin
188
188
  def define_pundit_policy(ar_model)
189
189
  if $available_actions.present?
190
190
  klass = Class.new(ApplicationPolicy) do
191
+ include CmAdmin::PermissionHelper
191
192
  $available_actions.each do |action|
192
193
  define_method "#{action.name}?".to_sym do
193
194
  return false unless Current.user.respond_to?(:cm_role_id)
194
195
  return false if Current.user.cm_role.nil?
196
+ return false if CmCurrent.user_permissions.empty?
195
197
 
196
- Current.user.cm_role.cm_permissions.where(action_name: action.name, ar_model_name: ar_model.name).present?
198
+ has_access_to?(ar_model, action)
197
199
  end
198
200
  end
199
201
  end
@@ -211,7 +213,7 @@ module CmAdmin
211
213
 
212
214
  define_method :resolve do
213
215
  # action_name = Current.request_params.dig("action")
214
- permission = Current.user.cm_role.cm_permissions.find_by(action_name: action.name, ar_model_name: ar_model.name)
216
+ permission = find_permission_by(ar_model, action)
215
217
  if permission.present? && permission.scope_name.present?
216
218
  scope.send(permission.scope_name)
217
219
  else
@@ -7,7 +7,7 @@ module CmAdmin
7
7
  attr_accessor :name, :display_name, :verb, :layout_type, :layout, :partial, :path, :page_title, :page_description,
8
8
  :child_records, :is_nested_field, :nested_table_name, :parent, :display_if, :route_type, :code_block,
9
9
  :display_type, :action_type, :redirection_url, :sort_direction, :sort_column, :icon_name, :scopes, :view_type,
10
- :kanban_attr, :model_name, :redirect_to, :execution_mode
10
+ :kanban_attr, :model_name, :redirect_to, :execution_mode, :eager_load_associations
11
11
 
12
12
  VALID_SORT_DIRECTION = Set[:asc, :desc].freeze
13
13
 
@@ -41,6 +41,7 @@ module CmAdmin
41
41
  self.route_type = nil
42
42
  self.display_type = nil
43
43
  self.view_type = :table
44
+ self.eager_load_associations = []
44
45
  self.kanban_attr = {}
45
46
  end
46
47
 
@@ -402,6 +402,17 @@ module CmAdmin
402
402
  @default_sort_column = default_column[:column]
403
403
  @default_sort_direction = default_column[:default_direction] if default_column[:default_direction].present?
404
404
  end
405
+
406
+ # Configure Eager load associations for action
407
+ # This will help us avoid N+1 queries
408
+ #
409
+ # @example Eager load associations
410
+ # eager_load_assocations [:association_name]
411
+ # eager_load_assocations [:constant]
412
+ #
413
+ def eager_load_associations(associations=[])
414
+ @current_action.eager_load_associations = associations if @current_action
415
+ end
405
416
  end
406
417
  end
407
418
  end
@@ -1,3 +1,3 @@
1
1
  module CmAdmin
2
- VERSION = '4.0.0'
2
+ VERSION = '4.1.1'
3
3
  end
@@ -13,6 +13,7 @@ module CmAdmin
13
13
  # Included Rails view helper
14
14
  include ActionView::Helpers::FormTagHelper
15
15
  include ActionView::Helpers::TagHelper
16
+ include ActionView::Helpers::NumberHelper
16
17
 
17
18
  def exportable(_klass, html_class: [])
18
19
  tag.a 'Export as excel', class: html_class.append('filter-btn modal-btn me-2'), data: { toggle: 'modal', target: '#exportmodal' } do
@@ -80,7 +81,7 @@ module CmAdmin
80
81
 
81
82
  def humanized_ar_collection_count(count, model_name)
82
83
  table_name = count == 1 ? model_name.singularize : model_name.pluralize
83
- "#{count} #{table_name.humanize.downcase} found"
84
+ "#{number_with_delimiter(count.to_i)} #{table_name.humanize.downcase} found"
84
85
  end
85
86
 
86
87
  def toast_message(message, toast_type)
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.0.0
4
+ version: 4.1.1
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-11-25 00:00:00.000000000 Z
17
+ date: 2024-12-02 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: caxlsx_rails
@@ -165,6 +165,7 @@ files:
165
165
  - ".github/workflows/deploy-yard-docs.yml"
166
166
  - ".github/workflows/linters.yml"
167
167
  - ".github/workflows/release-gem.yml"
168
+ - ".github/workflows/test.yml"
168
169
  - ".gitignore"
169
170
  - ".rspec"
170
171
  - ".rubocop-https---raw-githubusercontent-com-commutatus-cm-linters-main-rubocop-yml"
@@ -344,7 +345,9 @@ files:
344
345
  - app/controllers/cm_admin/static_controller.rb
345
346
  - app/helpers/cm_admin/application_helper.rb
346
347
  - app/helpers/cm_admin/custom_helper.rb
348
+ - app/helpers/cm_admin/permission_helper.rb
347
349
  - app/jobs/file_import_processor_job.rb
350
+ - app/models/cm_current.rb
348
351
  - app/models/cm_permission.rb
349
352
  - app/models/cm_role.rb
350
353
  - app/models/concerns/cm_admin/bulk_action_processor.rb