avo 1.6.4.pre.1 → 1.7.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +90 -69
  4. data/README.md +2 -2
  5. data/app/components/avo/edit/field_wrapper_component.html.erb +1 -1
  6. data/app/components/avo/edit/field_wrapper_component.rb +6 -1
  7. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +65 -15
  8. data/app/components/avo/fields/belongs_to_field/edit_component.rb +1 -1
  9. data/app/components/avo/fields/common/badge_viewer_component.html.erb +1 -1
  10. data/app/components/avo/fields/common/multiple_file_viewer_component.html.erb +1 -1
  11. data/app/components/avo/fields/common/single_file_viewer_component.html.erb +1 -1
  12. data/app/components/avo/fields/file_field/index_component.html.erb +1 -1
  13. data/app/components/avo/fields/trix_field/edit_component.html.erb +3 -0
  14. data/app/components/avo/index/grid_item_component.html.erb +4 -6
  15. data/app/components/avo/views/resource_index_component.html.erb +48 -46
  16. data/app/components/avo/views/resource_index_component.rb +1 -1
  17. data/app/controllers/avo/application_controller.rb +11 -9
  18. data/app/controllers/avo/base_controller.rb +21 -0
  19. data/app/controllers/avo/search_controller.rb +3 -1
  20. data/app/packs/js/controllers/fields/belongs_to_field_controller.js +65 -0
  21. data/app/packs/js/controllers/fields/trix_field_controller.js +16 -0
  22. data/app/views/avo/actions/show.html.erb +1 -1
  23. data/app/views/avo/attachments/show.html.erb +1 -1
  24. data/app/views/avo/base/_actions.html.erb +2 -2
  25. data/app/views/avo/home/index.html.erb +2 -2
  26. data/app/views/avo/partials/_javascript.html.erb +1 -1
  27. data/app/views/avo/partials/_resource_search.html.erb +1 -1
  28. data/app/views/avo/relations/new.html.erb +1 -1
  29. data/app/views/avo/sidebar/_sidebar.html.erb +1 -1
  30. data/db/factories.rb +4 -0
  31. data/lib/avo/app.rb +3 -1
  32. data/lib/avo/base_resource.rb +40 -43
  33. data/lib/avo/configuration.rb +7 -1
  34. data/lib/avo/fields/base_field.rb +5 -3
  35. data/lib/avo/fields/belongs_to_field.rb +96 -12
  36. data/lib/avo/fields/boolean_group_field.rb +1 -1
  37. data/lib/avo/fields/date_time_field.rb +1 -1
  38. data/lib/avo/fields/file_field.rb +0 -8
  39. data/lib/avo/fields/files_field.rb +1 -1
  40. data/lib/avo/fields/has_and_belongs_to_many_field.rb +4 -4
  41. data/lib/avo/fields/has_many_field.rb +4 -4
  42. data/lib/avo/fields/has_one_field.rb +5 -5
  43. data/lib/avo/fields/key_value_field.rb +1 -1
  44. data/lib/avo/fields/trix_field.rb +6 -0
  45. data/lib/avo/fields_collector.rb +0 -2
  46. data/lib/avo/version.rb +1 -1
  47. data/lib/generators/avo/field_generator.rb +1 -1
  48. data/lib/generators/avo/install_generator.rb +1 -1
  49. data/lib/generators/avo/templates/initializer/avo.tt +1 -0
  50. data/lib/generators/avo/templates/tool/sidebar_item.tt +1 -1
  51. data/public/avo-packs/css/application-797341b7.css.map +1 -1
  52. data/public/avo-packs/css/application-797341b7.css.map.br +0 -0
  53. data/public/avo-packs/css/application-797341b7.css.map.gz +0 -0
  54. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js +26 -0
  55. data/public/avo-packs/js/{application-b444cbf11135b4b23654.js.LICENSE.txt → application-651ed7b9bc727c83f673.js.LICENSE.txt} +0 -0
  56. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.br +0 -0
  57. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.gz +0 -0
  58. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map +1 -0
  59. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map.br +0 -0
  60. data/public/avo-packs/js/application-651ed7b9bc727c83f673.js.map.gz +0 -0
  61. data/public/avo-packs/manifest.json +8 -8
  62. metadata +12 -11
  63. data/public/avo-packs/js/application-b444cbf11135b4b23654.js +0 -26
  64. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.br +0 -0
  65. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.gz +0 -0
  66. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.map +0 -1
  67. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.map.br +0 -0
  68. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.map.gz +0 -0
@@ -1,5 +1,5 @@
1
1
  <%= javascript_tag nonce: true do %>
2
2
  window.Avo = window.Avo || { configuration: {} }
3
3
  Avo.configuration.timezone = '<%= Avo.configuration.timezone %>'
4
- Avo.configuration.root_path = '<%= Avo.configuration.root_path %>'
4
+ Avo.configuration.root_path = '<%= Avo::App.root_path %>'
5
5
  <% end %>
@@ -6,5 +6,5 @@
6
6
  data-debounce-timeout='<%= Avo.configuration.search_debounce %>'
7
7
  >
8
8
  </div>
9
- <div class="relative inline-flex text-gray-400 text-sm border border-gray-300 rounded-full cursor-pointer" data-search-target="button"></div>
9
+ <div class="hidden relative inline-flex text-gray-400 text-sm border border-gray-300 rounded-full cursor-pointer" data-search-target="button"></div>
10
10
  </div>
@@ -1,6 +1,6 @@
1
1
  <%= turbo_frame_tag 'attach_modal' do %>
2
2
  <%= form_with scope: 'fields',
3
- url: "#{Avo.configuration.root_path}/resources/#{params[:resource_name]}/#{params[:id]}/#{params[:related_name]}/",
3
+ url: "#{avo.root_path}resources/#{params[:resource_name]}/#{params[:id]}/#{params[:related_name]}/",
4
4
  data: {
5
5
  'turbo-frame': '_top'
6
6
  } do |form| %>
@@ -6,7 +6,7 @@
6
6
 
7
7
  <div class="flex-1 flex flex-col justify-between">
8
8
  <div class="tools py-4">
9
- <%= render Avo::NavigationLinkComponent.new label: 'Get started', path: Avo.configuration.root_path, active: :exclusive if Rails.env.development? && Avo.configuration.home_path.nil? %>
9
+ <%= render Avo::NavigationLinkComponent.new label: 'Get started', path: avo.root_path, active: :exclusive if Rails.env.development? && Avo.configuration.home_path.nil? %>
10
10
 
11
11
  <%= render Avo::NavigationHeadingComponent.new label: t('avo.resources') %>
12
12
 
data/db/factories.rb CHANGED
@@ -40,4 +40,8 @@ FactoryBot.define do
40
40
  meta { [{foo: "bar", hey: "hi"}, {bar: "baz"}, {hoho: "hohoho"}].sample }
41
41
  progress { Faker::Number.between(from: 0, to: 100) }
42
42
  end
43
+
44
+ factory :comment do
45
+ body { Faker::Lorem.paragraphs(number: rand(4...10)).join("\n") }
46
+ end
43
47
  end
data/lib/avo/app.rb CHANGED
@@ -7,6 +7,7 @@ module Avo
7
7
  class_attribute :context, default: nil
8
8
  class_attribute :license, default: nil
9
9
  class_attribute :current_user, default: nil
10
+ class_attribute :root_path, default: nil
10
11
 
11
12
  class << self
12
13
  def boot
@@ -21,10 +22,11 @@ module Avo
21
22
  end
22
23
  end
23
24
 
24
- def init(request:, context:, current_user:)
25
+ def init(request:, context:, current_user:, root_path:)
25
26
  self.request = request
26
27
  self.context = context
27
28
  self.current_user = current_user
29
+ self.root_path = root_path
28
30
 
29
31
  self.license = Licensing::LicenseManager.new(Licensing::HQ.new(request).response).license
30
32
 
@@ -22,6 +22,7 @@ module Avo
22
22
  class_attribute :fields
23
23
  class_attribute :grid_loader
24
24
  class_attribute :visible_on_sidebar, default: true
25
+ class_attribute :unscoped_queries_on_index, default: false
25
26
 
26
27
  class << self
27
28
  def grid(&block)
@@ -77,7 +78,7 @@ module Avo
77
78
  field.hydrate(resource: self, panel_name: default_panel_name, user: user)
78
79
  end
79
80
 
80
- if Avo::App.license.has_with_trial(:custom_fields)
81
+ if Avo::App.license.lacks_with_trial(:custom_fields)
81
82
  fields = fields.reject do |field|
82
83
  field.custom?
83
84
  end
@@ -87,20 +88,30 @@ module Avo
87
88
  end
88
89
 
89
90
  def get_fields(panel: nil, reflection: nil)
90
- fields = get_field_definitions.select do |field|
91
- field.send("show_on_#{@view}")
92
- end
91
+ fields = get_field_definitions
92
+ .select do |field|
93
+ field.send("show_on_#{@view}")
94
+ end
93
95
  .select do |field|
94
- field.visible?
95
- end
96
+ field.visible?
97
+ end
96
98
  .select do |field|
97
- unless field.respond_to?(:foreign_key) &&
98
- reflection.present? &&
99
- reflection.respond_to?(:foreign_key) &&
100
- reflection.foreign_key == field.foreign_key
99
+ # Strip out the reflection field in index queries with a parent association.
100
+ if reflection.present? &&
101
+ reflection.options.present? &&
102
+ field.respond_to?(:polymorphic_as) &&
103
+ field.polymorphic_as.to_s == reflection.options[:as].to_s
104
+ next
105
+ end
106
+ if field.respond_to?(:foreign_key) &&
107
+ reflection.present? &&
108
+ reflection.respond_to?(:foreign_key) &&
109
+ reflection.foreign_key != field.foreign_key
110
+ next
111
+ end
112
+
101
113
  true
102
114
  end
103
- end
104
115
 
105
116
  if panel.present?
106
117
  fields = fields.select do |field|
@@ -216,28 +227,6 @@ module Avo
216
227
  self.class.context
217
228
  end
218
229
 
219
- def query_search(via_resource_name:, via_resource_id:, user:, query: "")
220
- # model_class = self.model
221
-
222
- db_query = AuthorizationService.apply_policy(user, model_class)
223
-
224
- if via_resource_name.present?
225
- related_model = App.get_resource_by_name(via_resource_name).model
226
-
227
- db_query = related_model.find(via_resource_id).public_send(plural_name.downcase)
228
- end
229
-
230
- new_query = []
231
-
232
- [search].flatten.each_with_index do |search_by, index|
233
- new_query.push "or" if index != 0
234
-
235
- new_query.push "text(#{search_by}) ILIKE '%#{query}%'"
236
- end
237
-
238
- db_query.where(new_query.join(" "))
239
- end
240
-
241
230
  def attached_file_fields
242
231
  get_field_definitions.select do |field|
243
232
  [Avo::Fields::FileField, Avo::Fields::FilesField].include? field.class
@@ -250,14 +239,17 @@ module Avo
250
239
  .reject do |field|
251
240
  field.computed
252
241
  end
253
- .map { |field| [field.database_id(model).to_s, field] }.to_h
242
+ .map do |field|
243
+ [field.database_id(model).to_s, field]
244
+ end
245
+ .to_h
254
246
 
255
247
  params.each do |key, value|
256
248
  field = fields_by_database_id[key]
257
249
 
258
250
  next unless field.present?
259
251
 
260
- model = field.fill_field model, key, value
252
+ model = field.fill_field model, key, value, params
261
253
  end
262
254
 
263
255
  model
@@ -295,19 +287,22 @@ module Avo
295
287
 
296
288
  # We will not overwrite any attributes that come pre-filled in the model.
297
289
  def hydrate_model_with_default_values
298
- default_values = get_fields.select do |field|
299
- !field.computed
300
- end
290
+ default_values = get_fields
291
+ .select do |field|
292
+ !field.computed
293
+ end
301
294
  .map do |field|
302
295
  id = field.id
303
296
  value = field.value
304
297
 
305
- if field.respond_to? :foreign_key
298
+ if field.type == "belongs_to"
306
299
  id = field.foreign_key.to_sym
307
300
 
308
301
  reflection = @model._reflections[@params[:via_relation]]
309
302
 
310
- if reflection.present? && reflection.foreign_key.present? && field.id.to_s == @params[:via_relation].to_s
303
+ if field.polymorphic_as.present? && field.types.map(&:to_s).include?(@params["via_relation_class"])
304
+ value = @params["via_relation_class"].safe_constantize.find(@params[:via_resource_id])
305
+ elsif reflection.present? && reflection.foreign_key.present? && field.id.to_s == @params[:via_relation].to_s
311
306
  value = @params[:via_resource_id]
312
307
  end
313
308
  end
@@ -316,8 +311,8 @@ module Avo
316
311
  end
317
312
  .to_h
318
313
  .select do |id, value|
319
- value.present?
320
- end
314
+ value.present?
315
+ end
321
316
 
322
317
  default_values.each do |id, value|
323
318
  if @model.send(id).nil?
@@ -327,7 +322,7 @@ module Avo
327
322
  end
328
323
 
329
324
  def avo_path
330
- "#{Avo.configuration.root_path}/resources/#{model_class.model_name.route_key}/#{model.id}"
325
+ "#{Avo::App.root_path}/resources/#{model_class.model_name.route_key}/#{model.id}"
331
326
  end
332
327
 
333
328
  def label_field
@@ -355,6 +350,8 @@ module Avo
355
350
  def avatar
356
351
  return avatar_field.to_image if avatar_field.respond_to? :to_image
357
352
 
353
+ return avatar_field.value.variant(resize_to_limit: [480, 480]) if avatar_field.type == "file"
354
+
358
355
  avatar_field.value
359
356
  rescue
360
357
  nil
@@ -23,6 +23,7 @@ module Avo
23
23
  attr_accessor :initial_breadcrumbs
24
24
  attr_accessor :home_path
25
25
  attr_accessor :search_debounce
26
+ attr_accessor :view_component_path
26
27
 
27
28
  def initialize
28
29
  @root_path = "/avo"
@@ -58,6 +59,7 @@ module Avo
58
59
  @display_breadcrumbs = true
59
60
  @home_path = nil
60
61
  @search_debounce = 300
62
+ @view_component_path = "app/components"
61
63
  end
62
64
 
63
65
  def locale_tag
@@ -91,7 +93,7 @@ module Avo
91
93
  end
92
94
 
93
95
  def namespace
94
- root_path.delete "/"
96
+ computed_root_path.delete "/"
95
97
  end
96
98
 
97
99
  def root_path
@@ -99,6 +101,10 @@ module Avo
99
101
 
100
102
  @root_path
101
103
  end
104
+
105
+ def computed_root_path
106
+ Avo::App.root_path
107
+ end
102
108
  end
103
109
 
104
110
  def self.configuration
@@ -91,9 +91,11 @@ module Avo
91
91
  end
92
92
  end
93
93
 
94
- def value
94
+ def value(property = nil)
95
+ property ||= id
96
+
95
97
  # Get model value
96
- final_value = @model.send(id) if (model_or_class(@model) == "model") && @model.respond_to?(id)
98
+ final_value = @model.send(property) if (model_or_class(@model) == "model") && @model.respond_to?(property)
97
99
 
98
100
  if (@view === :new) || @action.present?
99
101
  final_value = if default.present? && default.respond_to?(:call)
@@ -114,7 +116,7 @@ module Avo
114
116
  final_value
115
117
  end
116
118
 
117
- def fill_field(model, key, value)
119
+ def fill_field(model, key, value, params)
118
120
  return model unless model.methods.include? key.to_sym
119
121
 
120
122
  model.send("#{key}=", value)
@@ -2,7 +2,9 @@ module Avo
2
2
  module Fields
3
3
  class BelongsToField < BaseField
4
4
  attr_reader :searchable
5
+ attr_reader :polymorphic_as
5
6
  attr_reader :relation_method
7
+ attr_reader :types
6
8
 
7
9
  def initialize(id, **args, &block)
8
10
  args[:placeholder] ||= I18n.t("avo.choose_an_option")
@@ -10,11 +12,17 @@ module Avo
10
12
  super(id, **args, &block)
11
13
 
12
14
  @searchable = args[:searchable] == true
15
+ @polymorphic_as = args[:polymorphic_as]
16
+ @types = args[:types]
13
17
  @relation_method = name.to_s.parameterize.underscore
14
18
  end
15
19
 
20
+ def value
21
+ super(polymorphic_as)
22
+ end
23
+
16
24
  def options
17
- target_resource.model_class.all.map do |model|
25
+ ::Avo::Services::AuthorizationService.apply_policy(user, target_resource.model_class).all.map do |model|
18
26
  {
19
27
  value: model.id,
20
28
  label: model.send(target_resource.class.title)
@@ -22,22 +30,32 @@ module Avo
22
30
  end
23
31
  end
24
32
 
33
+ def values_for_type(type)
34
+ ::Avo::Services::AuthorizationService.apply_policy(user, type).all.map do |model|
35
+ [model.send(App.get_resource_by_model_name(type).class.title), model.id]
36
+ end
37
+ end
38
+
25
39
  def database_value
26
40
  target_resource.id
27
41
  end
28
42
 
29
43
  def foreign_key
44
+ return polymorphic_as if polymorphic_as.present?
45
+
30
46
  if @model.present?
31
- if @model.instance_of?(Class)
32
- @model.reflections[@relation_method].foreign_key
33
- else
34
- @model.class.reflections[@relation_method].foreign_key
35
- end
36
- elsif @resource.present?
47
+ get_model_class(@model).reflections[@relation_method].foreign_key
48
+ elsif @resource.present? && @resource.model_class.reflections[@relation_method].present?
37
49
  @resource.model_class.reflections[@relation_method].foreign_key
38
50
  end
39
51
  end
40
52
 
53
+ def reflection_for_key(key)
54
+ get_model_class(get_model).reflections[key.to_s]
55
+ rescue
56
+ nil
57
+ end
58
+
41
59
  def relation_model_class
42
60
  @resource.model_class
43
61
  end
@@ -47,16 +65,82 @@ module Avo
47
65
  end
48
66
 
49
67
  def to_permitted_param
68
+ if polymorphic_as.present?
69
+ return ["#{polymorphic_as}_type".to_sym, "#{polymorphic_as}_id".to_sym]
70
+ end
71
+
50
72
  foreign_key.to_sym
51
73
  end
52
74
 
75
+ def fill_field(model, key, value, params)
76
+ return model unless model.methods.include? key.to_sym
77
+
78
+ if polymorphic_as.present?
79
+ model.send("#{polymorphic_as}_type=", params["#{polymorphic_as}_type"])
80
+
81
+ # If the type is blank, reset the id too.
82
+ if params["#{polymorphic_as}_type"].blank?
83
+ model.send("#{polymorphic_as}_id=", nil)
84
+ else
85
+ model.send("#{polymorphic_as}_id=", params["#{polymorphic_as}_id"])
86
+ end
87
+ else
88
+ model.send("#{key}=", value)
89
+ end
90
+
91
+ model
92
+ end
93
+
94
+ def database_id(model)
95
+ # If the field is a polymorphic value, return the polymorphic_type as key and pre-fill the _id in fill_field.
96
+ return "#{polymorphic_as}_type" if polymorphic_as.present?
97
+
98
+ foreign_key
99
+ rescue
100
+ id
101
+ end
102
+
53
103
  def target_resource
54
- if @model._reflections[id.to_s].klass.present?
55
- App.get_resource_by_model_name @model._reflections[id.to_s].klass.to_s
56
- elsif @model._reflections[id.to_s].options[:class_name].present?
57
- App.get_resource_by_model_name @model._reflections[id.to_s].options[:class_name]
104
+ if polymorphic_as.present?
105
+ if value.present?
106
+ return App.get_resource_by_model_name(value.class)
107
+ else
108
+ return nil
109
+ end
110
+ end
111
+
112
+ reflection_key = polymorphic_as || id
113
+
114
+ if @model._reflections[reflection_key.to_s].klass.present?
115
+ App.get_resource_by_model_name @model._reflections[reflection_key.to_s].klass.to_s
116
+ elsif @model._reflections[reflection_key.to_s].options[:class_name].present?
117
+ App.get_resource_by_model_name @model._reflections[reflection_key.to_s].options[:class_name]
118
+ else
119
+ App.get_resource_by_name reflection_key.to_s
120
+ end
121
+ end
122
+
123
+ def get_model
124
+ return @model if @model.present?
125
+
126
+ @resource.model
127
+ rescue
128
+ nil
129
+ end
130
+
131
+ def name
132
+ return polymorphic_as.to_s.humanize if polymorphic_as.present? && view == :index
133
+
134
+ super
135
+ end
136
+
137
+ private
138
+
139
+ def get_model_class(model)
140
+ if model.instance_of?(Class)
141
+ model
58
142
  else
59
- App.get_resource_by_name id.to_s
143
+ model.class
60
144
  end
61
145
  end
62
146
  end
@@ -13,7 +13,7 @@ module Avo
13
13
  ["#{id}": []]
14
14
  end
15
15
 
16
- def fill_field(model, key, value)
16
+ def fill_field(model, key, value, params)
17
17
  new_value = {}
18
18
 
19
19
  # Filter out the empty ("") value boolean group generates