avo 3.10.6 → 3.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -2
  3. data/README.md +4 -9
  4. data/app/components/avo/fields/boolean_field/index_component.html.erb +1 -1
  5. data/app/components/avo/fields/boolean_field/show_component.html.erb +1 -1
  6. data/app/components/avo/fields/boolean_group_field/edit_component.rb +1 -1
  7. data/app/components/avo/fields/common/boolean_group_component.html.erb +3 -6
  8. data/app/components/avo/resource_component.rb +0 -2
  9. data/app/components/avo/turbo_frame_wrapper_component.html.erb +1 -1
  10. data/app/controllers/avo/base_controller.rb +9 -2
  11. data/lib/avo/asset_manager.rb +8 -3
  12. data/lib/avo/base_resource.rb +2 -601
  13. data/lib/avo/fields/boolean_group_field.rb +1 -1
  14. data/lib/avo/licensing/request.rb +3 -1
  15. data/lib/avo/resources/base.rb +607 -0
  16. data/lib/avo/resources/items/item_group.rb +2 -0
  17. data/lib/avo/resources/items/tab.rb +2 -0
  18. data/lib/avo/version.rb +1 -1
  19. data/lib/avo.rb +1 -0
  20. data/lib/generators/avo/eject_generator.rb +1 -1
  21. data/lib/generators/avo/templates/locales/avo.de.yml +120 -0
  22. data/lib/generators/avo/templates/locales/avo.it.yml +120 -0
  23. data/lib/generators/avo/templates/locales/avo.nl.yml +120 -0
  24. data/lib/generators/avo/templates/locales/avo.pl.yml +120 -0
  25. data/lib/generators/avo/templates/locales/avo.ru.yml +120 -0
  26. data/lib/generators/avo/templates/locales/avo.uk.yml +120 -0
  27. data/lib/generators/avo/templates/locales/avo.zh.yml +120 -0
  28. data/public/avo-assets/avo.base.css +3 -71
  29. data/public/avo-assets/avo.base.js +230 -227
  30. data/public/avo-assets/avo.base.js.map +4 -4
  31. data/public/avo-assets/logo-on-white.png +0 -0
  32. metadata +10 -2
@@ -1,604 +1,5 @@
1
1
  module Avo
2
- class BaseResource
3
- extend ActiveSupport::DescendantsTracker
4
-
5
- include ActionView::Helpers::UrlHelper
6
- include Avo::Concerns::HasItems
7
- include Avo::Concerns::CanReplaceItems
8
- include Avo::Concerns::HasControls
9
- include Avo::Concerns::HasResourceStimulusControllers
10
- include Avo::Concerns::ModelClassConstantized
11
- include Avo::Concerns::HasDescription
12
- include Avo::Concerns::HasCoverPhoto
13
- include Avo::Concerns::HasProfilePhoto
14
- include Avo::Concerns::HasHelpers
15
- include Avo::Concerns::Hydration
16
- include Avo::Concerns::Pagination
17
-
18
- # Avo::Current methods
19
- delegate :context, to: Avo::Current
20
- def current_user
21
- Avo::Current.user
22
- end
23
- delegate :params, to: Avo::Current
24
- delegate :request, to: Avo::Current
25
- delegate :view_context, to: Avo::Current
26
-
27
- # view_context methods
28
- delegate :simple_format, :content_tag, to: :view_context
29
- delegate :main_app, to: :view_context
30
- delegate :avo, to: :view_context
31
- delegate :resource_path, to: :view_context
32
- delegate :resources_path, to: :view_context
33
-
34
- # I18n methods
35
- delegate :t, to: ::I18n
36
-
37
- # class methods
38
- delegate :class_name, to: :class
39
- delegate :route_key, to: :class
40
- delegate :singular_route_key, to: :class
41
-
42
- attr_accessor :view
43
- attr_accessor :reflection
44
- attr_accessor :user
45
- attr_accessor :record
46
-
47
- class_attribute :id, default: :id
48
- class_attribute :title
49
- class_attribute :search, default: {}
50
- class_attribute :includes, default: []
51
- class_attribute :attachments, default: []
52
- class_attribute :single_includes, default: []
53
- class_attribute :single_attachments, default: []
54
- class_attribute :authorization_policy
55
- class_attribute :translation_key
56
- class_attribute :default_view_type, default: :table
57
- class_attribute :devise_password_optional, default: false
58
- class_attribute :scopes_loader
59
- class_attribute :filters_loader
60
- class_attribute :view_types
61
- class_attribute :grid_view
62
- class_attribute :visible_on_sidebar, default: true
63
- class_attribute :index_query, default: -> {
64
- query
65
- }
66
- class_attribute :find_record_method, default: -> {
67
- query.find id
68
- }
69
- class_attribute :after_create_path, default: :show
70
- class_attribute :after_update_path, default: :show
71
- class_attribute :record_selector, default: true
72
- class_attribute :keep_filters_panel_open, default: false
73
- class_attribute :extra_params
74
- class_attribute :link_to_child_resource, default: false
75
- class_attribute :map_view
76
- class_attribute :components, default: {}
77
-
78
- # EXTRACT:
79
- class_attribute :ordering
80
-
81
- class << self
82
- delegate :t, to: ::I18n
83
- delegate :context, to: ::Avo::Current
84
-
85
- def action(action_class, arguments: {})
86
- deprecated_dsl_api __method__, "actions"
87
- end
88
-
89
- def filter(filter_class, arguments: {})
90
- deprecated_dsl_api __method__, "filters"
91
- end
92
-
93
- def scope(scope_class)
94
- deprecated_dsl_api __method__, "scopes"
95
- end
96
-
97
- # This resolves the scope when doing "where" queries (not find queries)
98
- #
99
- # It's used to apply the authorization feature.
100
- def query_scope
101
- authorization.apply_policy Avo::ExecutionContext.new(
102
- target: index_query,
103
- query: model_class
104
- ).handle
105
- end
106
-
107
- # This resolves the scope when finding records (not "where" queries)
108
- #
109
- # It's used to apply the authorization feature.
110
- def find_scope
111
- authorization.apply_policy model_class
112
- end
113
-
114
- def authorization
115
- Avo::Services::AuthorizationService.new Avo::Current.user, model_class, policy_class: authorization_policy
116
- end
117
-
118
- def valid_association_name(record, association_name)
119
- association_name if record._reflections.with_indifferent_access[association_name].present?
120
- end
121
-
122
- def valid_attachment_name(record, association_name)
123
- association_name if record.class.reflect_on_attachment(association_name).present?
124
- end
125
-
126
- def get_available_models
127
- ApplicationRecord.descendants
128
- end
129
-
130
- def get_model_by_name(model_name)
131
- get_available_models.find do |m|
132
- m.to_s == model_name.to_s
133
- end
134
- end
135
-
136
- # Returns the model class being used for this resource.
137
- #
138
- # The Resource instance has a model_class method too so it can support the STI use cases
139
- # where we figure out the model class from the record
140
- def model_class(record_class: nil)
141
- # get the model class off of the static property
142
- return @model_class if @model_class.present?
143
-
144
- # get the model class off of the record for STI models
145
- return record_class if record_class.present?
146
-
147
- # generate a model class
148
- class_name.safe_constantize
149
- end
150
-
151
- # This is used as the model class ID
152
- # We use this instead of the route_key to maintain compatibility with uncountable models
153
- # With uncountable models route key appends an _index suffix (Fish->fish_index)
154
- # Example: User->users, MediaItem->media_items, Fish->fish
155
- def model_key
156
- @model_key ||= model_class.model_name.plural
157
- end
158
-
159
- def class_name
160
- @class_name ||= to_s.demodulize
161
- end
162
-
163
- def route_key
164
- class_name.underscore.pluralize
165
- end
166
-
167
- def singular_route_key
168
- route_key.singularize
169
- end
170
-
171
- def translation_key
172
- @translation_key || "avo.resource_translations.#{class_name.underscore}"
173
- end
174
-
175
- def name
176
- @name ||= name_from_translation_key(count: 1, default: class_name.underscore.humanize)
177
- end
178
- alias_method :singular_name, :name
179
-
180
- def plural_name
181
- name_from_translation_key(count: 2, default: name.pluralize)
182
- end
183
-
184
- # Get the name from the translation_key and fallback to default
185
- # It can raise I18n::InvalidPluralizationData when using only resource_translation without pluralization keys like: one, two or other key
186
- # Example:
187
- # ---
188
- # en:
189
- # avo:
190
- # resource_translations:
191
- # product:
192
- # save: "Save product"
193
- def name_from_translation_key(count:, default:)
194
- t(translation_key, count:, default:).humanize
195
- rescue I18n::InvalidPluralizationData
196
- default
197
- end
198
-
199
- def underscore_name
200
- return @name if @name.present?
201
-
202
- name.demodulize.underscore
203
- end
204
-
205
- def navigation_label
206
- plural_name.humanize
207
- end
208
-
209
- def find_record(id, query: nil, params: nil)
210
- query ||= find_scope # If no record is given we'll use the default
211
-
212
- if single_includes.present?
213
- query = query.includes(*single_includes)
214
- end
215
-
216
- if single_attachments.present?
217
- single_attachments.each do |attachment|
218
- query = query.send(:"with_attached_#{attachment}")
219
- end
220
- end
221
-
222
- Avo::ExecutionContext.new(
223
- target: find_record_method,
224
- query: query,
225
- id: id,
226
- params: params
227
- ).handle
228
- end
229
-
230
- def search_query
231
- search.dig(:query)
232
- end
233
-
234
- def search_results_count
235
- search.dig(:results_count)
236
- end
237
-
238
- def fetch_search(key, record: nil)
239
- # self.class.fetch_search
240
- Avo::ExecutionContext.new(target: search[key], resource: self, record: record).handle
241
- end
242
- end
243
-
244
- delegate :context, to: ::Avo::Current
245
- delegate :name, to: :class
246
- delegate :singular_name, to: :class
247
- delegate :plural_name, to: :class
248
- delegate :underscore_name, to: :class
249
- delegate :to_param, to: :class
250
- delegate :find_record, to: :class
251
- delegate :model_key, to: :class
252
- delegate :tab, to: :items_holder
253
-
254
- def initialize(record: nil, view: nil, user: nil, params: nil)
255
- @view = Avo::ViewInquirer.new(view) if view.present?
256
- @user = user if user.present?
257
- @params = params if params.present?
258
-
259
- if record.present?
260
- @record = record
261
-
262
- hydrate_model_with_default_values if @view&.new?
263
- end
264
-
265
- unless self.class.model_class.present?
266
- if model_class.present? && model_class.respond_to?(:base_class)
267
- self.class.model_class = model_class.base_class
268
- end
269
- end
270
- end
271
-
272
- def detect_fields
273
- self.items_holder = Avo::Resources::Items::Holder.new(parent: self)
274
-
275
- # Used in testing to replace items
276
- if temporary_items.present?
277
- instance_eval(&temporary_items)
278
- else
279
- fetch_fields
280
- end
281
-
282
- self
283
- end
284
-
285
- VIEW_METHODS_MAPPING = {
286
- index: [:index_fields, :display_fields],
287
- show: [:show_fields, :display_fields],
288
- edit: [:edit_fields, :form_fields],
289
- update: [:edit_fields, :form_fields],
290
- new: [:new_fields, :form_fields],
291
- create: [:new_fields, :form_fields]
292
- } unless defined? VIEW_METHODS_MAPPING
293
-
294
- def fetch_fields
295
- possible_methods_for_view = VIEW_METHODS_MAPPING[view.to_sym]
296
-
297
- # Safe navigation operator is used because the view can be "destroy" or "preview"
298
- possible_methods_for_view&.each do |method_for_view|
299
- return send(method_for_view) if respond_to?(method_for_view)
300
- end
301
-
302
- fields
303
- end
304
-
305
- def fetch_cards
306
- cards
307
- end
308
-
309
- def divider(label = nil)
310
- entity_loader(:action).use({class: Divider, label: label}.compact)
311
- end
312
-
313
- # def fields / def cards
314
- [:fields, :cards].each do |method_name|
315
- define_method method_name do
316
- # Empty method
317
- end
318
- end
319
-
320
- [:action, :filter, :scope].each do |entity|
321
- plural_entity = entity.to_s.pluralize
322
-
323
- # def actions / def filters / def scopes
324
- define_method plural_entity do
325
- # blank entity method
326
- end
327
-
328
- # def action / def filter / def scope
329
- define_method entity do |entity_class, arguments: {}, icon: nil|
330
- entity_loader(entity).use({class: entity_class, arguments: arguments, icon: icon}.compact)
331
- end
332
-
333
- # def get_actions / def get_filters / def get_scopes
334
- define_method "get_#{plural_entity}" do
335
- return entity_loader(entity).bag if entity_loader(entity).present?
336
-
337
- # ex: @actions_loader = Avo::Loaders::ActionsLoader.new
338
- instance_variable_set(
339
- "@#{plural_entity}_loader",
340
- "Avo::Loaders::#{plural_entity.humanize}Loader".constantize.new
341
- )
342
-
343
- send plural_entity
344
-
345
- entity_loader(entity).bag
346
- end
347
-
348
- # def get_action_arguments / def get_filter_arguments / def get_scope_arguments
349
- define_method "get_#{entity}_arguments" do |entity_class|
350
- klass = send("get_#{plural_entity}").find { |entity| entity[:class].to_s == entity_class.to_s }
351
-
352
- raise "Couldn't find '#{entity_class}' in the 'def #{plural_entity}' method on your '#{self.class}' resource." if klass.nil?
353
-
354
- klass[:arguments]
355
- end
356
- end
357
-
358
- def hydrate(...)
359
- super(...)
360
-
361
- if @record.present?
362
- hydrate_model_with_default_values if @view&.new?
363
- end
364
-
365
- self
366
- end
367
-
368
- def default_panel_name
369
- return @params[:related_name].capitalize if @params.present? && @params[:related_name].present?
370
-
371
- case @view.to_sym
372
- when :show
373
- record_title
374
- when :edit
375
- record_title
376
- when :new
377
- t("avo.create_new_item", item: name.humanize(capitalize: false)).upcase_first
378
- end
379
- end
380
-
381
- # Returns the model class being used for this resource.
382
- #
383
- # We use the class method as a fallback but we pass it the record too so it can support the STI use cases
384
- # where we figure out the model class from that record.
385
- def model_class
386
- record_class = @record&.class
387
-
388
- self.class.model_class record_class: record_class
389
- end
390
-
391
- def record_title
392
- return name if @record.nil?
393
-
394
- # Get the title from the record if title is not set, try to get the name, title or label, or fallback to the id
395
- return @record.try(:name) || @record.try(:title) || @record.try(:label) || @record.id if title.nil?
396
-
397
- # If the title is a symbol, get the value from the record else execute the block/string
398
- case title
399
- when Symbol
400
- @record.send title
401
- when Proc
402
- Avo::ExecutionContext.new(target: title, resource: self, record: @record).handle
403
- end
404
- end
405
-
406
- def available_view_types
407
- if self.class.view_types.present?
408
- return Array(
409
- Avo::ExecutionContext.new(
410
- target: self.class.view_types,
411
- resource: self,
412
- record: record
413
- ).handle
414
- )
415
- end
416
-
417
- view_types = [:table]
418
-
419
- view_types << :grid if self.class.grid_view.present?
420
- view_types << :map if map_view.present?
421
-
422
- view_types
423
- end
424
-
425
- def attachment_fields
426
- get_field_definitions.select do |field|
427
- [Avo::Fields::FileField, Avo::Fields::FilesField].include? field.class
428
- end
429
- end
430
-
431
- # Map the received params to their actual fields
432
- def fields_by_database_id
433
- get_field_definitions
434
- .reject do |field|
435
- field.computed
436
- end
437
- .map do |field|
438
- [field.database_id.to_s, field]
439
- end
440
- .to_h
441
- end
442
-
443
- def fill_record(record, params, extra_params: [])
444
- # Write the field values
445
- params.each do |key, value|
446
- field = fields_by_database_id[key]
447
-
448
- next unless field.present?
449
-
450
- record = field.fill_field record, key, value, params
451
- end
452
-
453
- # Write the user configured extra params to the record
454
- if extra_params.present?
455
- # Let Rails fill in the rest of the params
456
- record.assign_attributes params.permit(extra_params)
457
- end
458
-
459
- record
460
- end
461
-
462
- def authorization(user: nil)
463
- current_user = user || Avo::Current.user
464
- Avo::Services::AuthorizationService.new(current_user, record || model_class, policy_class: authorization_policy)
465
- end
466
-
467
- def file_hash
468
- content_to_be_hashed = ""
469
-
470
- resource_path = Rails.root.join("app", "avo", "resources", "#{file_name}.rb").to_s
471
- if File.file? resource_path
472
- content_to_be_hashed += File.read(resource_path)
473
- end
474
-
475
- # policy file hash
476
- policy_path = Rails.root.join("app", "policies", "#{file_name.gsub("_resource", "")}_policy.rb").to_s
477
- if File.file? policy_path
478
- content_to_be_hashed += File.read(policy_path)
479
- end
480
-
481
- Digest::MD5.hexdigest(content_to_be_hashed)
482
- end
483
-
484
- def file_name
485
- @file_name ||= self.class.underscore_name.tr(" ", "_")
486
- end
487
-
488
- def cache_hash(parent_record)
489
- result = [record, file_hash]
490
-
491
- if parent_record.present?
492
- result << parent_record
493
- end
494
-
495
- result
496
- end
497
-
498
- # We will not overwrite any attributes that come pre-filled in the record.
499
- def hydrate_model_with_default_values
500
- default_values = get_fields
501
- .select do |field|
502
- !field.computed && !field.is_a?(Avo::Fields::HeadingField)
503
- end
504
- .map do |field|
505
- value = field.value
506
-
507
- if field.type == "belongs_to"
508
-
509
- reflection = @record._reflections.with_indifferent_access[@params[:via_relation]]
510
-
511
- if field.polymorphic_as.present? && field.types.map(&:to_s).include?(@params[:via_relation_class])
512
- # set the value to the actual record
513
- via_resource = Avo.resource_manager.get_resource_by_model_class(@params[:via_relation_class])
514
- value = via_resource.find_record(@params[:via_record_id])
515
- elsif reflection.present? && reflection.foreign_key.present? && field.id.to_s == @params[:via_relation].to_s
516
- resource = Avo.resource_manager.get_resource_by_model_class params[:via_relation_class]
517
- record = resource.find_record @params[:via_record_id], params: params
518
- id_param = reflection.options[:primary_key] || :id
519
-
520
- value = record.send(id_param)
521
- end
522
- end
523
-
524
- [field, value]
525
- end
526
- .to_h
527
- .select do |_, value|
528
- value.present?
529
- end
530
-
531
- default_values.each do |field, value|
532
- field.assign_value record: @record, value: value
533
- end
534
- end
535
-
536
- def model_name
537
- model_class.model_name
538
- end
539
-
540
- def singular_model_key
541
- model_class.model_name.singular
542
- end
543
-
544
- def record_path
545
- resource_path(record: record, resource: self)
546
- end
547
-
548
- def records_path
549
- resources_path(resource: self)
550
- end
551
-
552
- def avatar_field
553
- get_field_definitions.find do |field|
554
- field.as_avatar.present?
555
- end
556
- rescue
557
- nil
558
- end
559
-
560
- def avatar
561
- return avatar_field.to_image if avatar_field.respond_to? :to_image
562
-
563
- return avatar_field.value.variant(resize_to_limit: [480, 480]) if avatar_field.type == "file"
564
-
565
- avatar_field.value
566
- rescue
567
- nil
568
- end
569
-
570
- def avatar_type
571
- avatar_field.as_avatar
572
- rescue
573
- nil
574
- end
575
-
576
- def form_scope
577
- model_class.base_class.to_s.underscore.downcase
578
- end
579
-
580
- def has_record_id?
581
- record.present? && record_id.present?
582
- end
583
-
584
- def id_attribute
585
- :id
586
- end
587
-
588
- def record_id
589
- record.send(id_attribute)
590
- end
591
-
592
- def description_attributes
593
- {
594
- view: view,
595
- resource: self,
596
- record: record
597
- }
598
- end
599
-
600
- def entity_loader(entity)
601
- instance_variable_get("@#{entity.to_s.pluralize}_loader")
602
- end
2
+ class BaseResource < Avo::Resources::Base
3
+ # Users can override this class to add custom methods for all resources.
603
4
  end
604
5
  end
@@ -33,7 +33,7 @@ module Avo
33
33
  end
34
34
 
35
35
  # Don't override existing values unless specified in options
36
- record[id] = (record[id] || {}).merge(new_value)
36
+ record.send(:"#{id}=", (record.send(id) || {}).merge(new_value))
37
37
 
38
38
  record
39
39
  end
@@ -1,3 +1,5 @@
1
+ require "net/http"
2
+
1
3
  module Avo
2
4
  module Licensing
3
5
  class Request
@@ -8,7 +10,7 @@ module Avo
8
10
  http.use_ssl = (uri.scheme == "https")
9
11
  http.read_timeout = timeout
10
12
  http.open_timeout = timeout
11
- request = Net::HTTP::Post.new(uri.request_uri, {'Content-Type' => 'application/json'})
13
+ request = Net::HTTP::Post.new(uri.request_uri, {"Content-Type" => "application/json"})
12
14
  request.body = body
13
15
  http.request(request)
14
16
  end