avo 1.6.3.pre.2 → 1.7.2

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +2 -2
  3. data/README.md +2 -2
  4. data/app/components/avo/edit/field_wrapper_component.html.erb +1 -1
  5. data/app/components/avo/edit/field_wrapper_component.rb +6 -1
  6. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +65 -15
  7. data/app/components/avo/fields/belongs_to_field/edit_component.rb +1 -1
  8. data/app/components/avo/fields/common/badge_viewer_component.html.erb +1 -1
  9. data/app/controllers/avo/base_controller.rb +5 -0
  10. data/app/packs/js/controllers/fields/belongs_to_field_controller.js +65 -0
  11. data/lib/avo/base_resource.rb +23 -37
  12. data/lib/avo/fields/base_field.rb +1 -1
  13. data/lib/avo/fields/belongs_to_field.rb +48 -6
  14. data/lib/avo/fields/boolean_group_field.rb +1 -1
  15. data/lib/avo/fields/date_time_field.rb +1 -1
  16. data/lib/avo/fields/files_field.rb +1 -1
  17. data/lib/avo/fields/has_one_field.rb +1 -1
  18. data/lib/avo/fields/key_value_field.rb +1 -1
  19. data/lib/avo/fields/select_field.rb +17 -4
  20. data/lib/avo/fields_collector.rb +0 -2
  21. data/lib/avo/version.rb +1 -1
  22. data/public/avo-packs/css/application-797341b7.css.map +1 -1
  23. data/public/avo-packs/css/application-797341b7.css.map.br +0 -0
  24. data/public/avo-packs/css/application-797341b7.css.map.gz +0 -0
  25. data/public/avo-packs/js/application-2a4345ecd9464d1e8850.js +26 -0
  26. data/public/avo-packs/js/{application-b444cbf11135b4b23654.js.LICENSE.txt → application-2a4345ecd9464d1e8850.js.LICENSE.txt} +0 -0
  27. data/public/avo-packs/js/application-2a4345ecd9464d1e8850.js.br +0 -0
  28. data/public/avo-packs/js/application-2a4345ecd9464d1e8850.js.gz +0 -0
  29. data/public/avo-packs/js/application-2a4345ecd9464d1e8850.js.map +1 -0
  30. data/public/avo-packs/js/application-2a4345ecd9464d1e8850.js.map.br +0 -0
  31. data/public/avo-packs/js/application-2a4345ecd9464d1e8850.js.map.gz +0 -0
  32. data/public/avo-packs/manifest.json +8 -8
  33. metadata +12 -11
  34. data/public/avo-packs/js/application-b444cbf11135b4b23654.js +0 -26
  35. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.br +0 -0
  36. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.gz +0 -0
  37. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.map +0 -1
  38. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.map.br +0 -0
  39. data/public/avo-packs/js/application-b444cbf11135b4b23654.js.map.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bba2852b5719783587af150d06434a6085a00cf84e57ffc0dc6260b8e0d31ba1
4
- data.tar.gz: 9517eeaad5198404857b26b7f7b971a418b863a14d9554de1c43c5c714bc0da8
3
+ metadata.gz: 76e6f84b0b7d8c9144994b898621dd99359875e7a32e26865c86034431031992
4
+ data.tar.gz: 8df9ac786016d2ba0ef2ca2071a4d9e3445a6d3e0db3d55fa4ac12aa49d1fe79
5
5
  SHA512:
6
- metadata.gz: 00fb34bedfcfb67db9febdb0cb71eebeda243b34f3d18e67ae84fa71150310d24af72c3c234f4b5c742a1c13e9d6a77dcbeabacfe98049ecef210bc08c8f4e82
7
- data.tar.gz: d8c7c6425790b622433ca48fe54647dd48b7df204c380c143e3e89438cebafa96ea09d38500f6514073a15c8f83970d67bc3313d531c4b5073e322ce9990231e
6
+ metadata.gz: 0ef42c3022b80c94f2fa5543321a737d7c89fe4a57ee81493fdd3c9cd068184c8107873985e9df3a0d8d33fef2ccf664f7bd70094349925a6b04f94375cb0e92
7
+ data.tar.gz: 68fa168d75070ff5dd08f6b5643ecf9efa73a003b5fb3968bb8b784594553cf76043a8598397cb6a392fcae958865f52f530126df3825df3f132656dbfe3b770
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (1.6.3.pre.2)
4
+ avo (1.7.2)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -333,7 +333,7 @@ GEM
333
333
  thread_safe (~> 0.1)
334
334
  unicode-display_width (2.0.0)
335
335
  unicode_utils (1.4.0)
336
- view_component (2.28.0)
336
+ view_component (2.32.0)
337
337
  activesupport (>= 5.0.0, < 7.0)
338
338
  warden (1.2.9)
339
339
  rack (>= 2.0.9)
data/README.md CHANGED
@@ -56,6 +56,6 @@ $ bundle install
56
56
 
57
57
  Please read [CONTRIBUTING.MD](./CONTRIBUTING.MD)
58
58
 
59
- # Upgrade guide
59
+ # Upgrade Guide
60
60
 
61
- Please read the [UPGRADE_GUIDE.MD](https://docs.avohq.io/0.5.x/upgrade.html)
61
+ Please read the [UPGRADE_GUIDE.MD](https://docs.avohq.io/1.0/upgrade.html)
@@ -1,7 +1,7 @@
1
1
  <div class="flex items-center py-0 leading-tight <%= @classes %> min-h-16" data-field-id="<%= @field.id %>" data-field-type="<%= @field.type %>">
2
2
  <div class="h-16 flex self-start items-center text-blue-gray-800">
3
3
  <div class="<% if @displayed_in_modal %> md:w-48 <% else %> md:w-64 <% end %> w-48 px-8 flex" data-slot="label">
4
- <%= @form.label @field.id, @field.name %> <% if @field.required %> <span class="text-red-600">*</span> <% end %>
4
+ <%= @form.label @field.id, label %> <% if @field.required %> <span class="text-red-600">*</span> <% end %>
5
5
  </div>
6
6
  </div>
7
7
  <div class="flex-1 flex flex-row min-h-inherit">
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::Edit::FieldWrapperComponent < ViewComponent::Base
4
- def initialize(field: nil, dash_if_blank: true, full_width: false, displayed_in_modal: false, form: nil, resource: {}, **args)
4
+ def initialize(field: nil, dash_if_blank: true, full_width: false, displayed_in_modal: false, form: nil, resource: {}, label: nil, **args)
5
5
  @field = field
6
6
  @dash_if_blank = dash_if_blank
7
7
  @classes = args[:class].present? ? args[:class] : ""
@@ -11,9 +11,14 @@ class Avo::Edit::FieldWrapperComponent < ViewComponent::Base
11
11
  @resource = resource
12
12
  @model = resource.present? ? resource.model : nil
13
13
  @full_width = full_width
14
+ @label = label
14
15
 
15
16
  if (@index != 0) || @displayed_in_modal
16
17
  @classes += " border-t"
17
18
  end
18
19
  end
20
+
21
+ def label
22
+ @label || @field.name
23
+ end
19
24
  end
@@ -1,18 +1,68 @@
1
- <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
2
- <%= @form.select @field.foreign_key, @field.options.map { |o| [o[:label], o[:value]] },
3
- {
4
- include_blank: @field.placeholder,
5
- },
6
- {
7
- class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
8
- disabled: disabled
9
- }
1
+ <% if @field.types.present? %>
2
+ <div data-controller="belongs-to-field">
3
+ <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
4
+ <%= @form.select "#{@field.foreign_key}_type", @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
5
+ {
6
+ include_blank: @field.placeholder,
7
+ },
8
+ {
9
+ class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
10
+ disabled: disabled,
11
+ 'data-belongs-to-field-target': "select",
12
+ 'data-action': 'change->belongs-to-field#changedType'
13
+ }
14
+ %>
15
+ <%
16
+ # If the select field is disabled, no value will be sent. It's how HTML works.
17
+ # Thus the extra hidden field to actually send the related id to the server.
18
+ if disabled
19
+ %>
20
+ <%= @form.hidden_field "#{@field.foreign_key}_type" %>
21
+ <% end %>
22
+ <% end %>
23
+ <% @field.types.each do |type| %>
24
+ <div class="hidden"
25
+ data-belongs-to-field-target="type"
26
+ data-type="<%= type %>"
27
+ >
28
+ <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, label: type.to_s.underscore.humanize do %>
29
+ <%= @form.select "#{@field.foreign_key}_id", @field.values_for_type(type),
30
+ {
31
+ include_blank: @field.placeholder,
32
+ },
33
+ {
34
+ class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
35
+ disabled: disabled
36
+ }
37
+ %>
38
+ <%
39
+ # If the select field is disabled, no value will be sent. It's how HTML works.
40
+ # Thus the extra hidden field to actually send the related id to the server.
41
+ if disabled
42
+ %>
43
+ <%= @form.hidden_field "#{@field.foreign_key}_id" %>
44
+ <% end %>
45
+ <% end %>
46
+ </div>
47
+ <% end %>
48
+ </div>
49
+ <% else %>
50
+ <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
51
+ <%= @form.select @field.foreign_key, @field.options.map { |o| [o[:label], o[:value]] },
52
+ {
53
+ include_blank: @field.placeholder,
54
+ },
55
+ {
56
+ class: helpers.input_classes('w-full', has_error: @field.model_errors.include?(@field.id)),
57
+ disabled: disabled
58
+ }
59
+ %>
60
+ <%
61
+ # If the select field is disabled, no value will be sent. It's how HTML works.
62
+ # Thus the extra hidden field to actually send the related id to the server.
63
+ if disabled
10
64
  %>
11
- <%
12
- # If the select field is disabled, no value will be sent. It's how HTML works.
13
- # Thus the extra hidden field to actually send the related id to the server.
14
- if disabled
15
- %>
16
- <%= @form.hidden_field @field.foreign_key %>
65
+ <%= @form.hidden_field @field.foreign_key %>
66
+ <% end %>
17
67
  <% end %>
18
68
  <% end %>
@@ -3,7 +3,7 @@
3
3
  class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent
4
4
  def disabled
5
5
  return true if @field.readonly
6
- return true if @field.target_resource.model_class.name == params[:via_resource_class]
6
+ return true if @field.target_resource.present? && @field.target_resource.model_class.name == params[:via_resource_class]
7
7
  return true if @field.id.to_s == params[:via_relation].to_s
8
8
 
9
9
  false
@@ -10,7 +10,7 @@
10
10
  background = :info
11
11
 
12
12
  @options.invert.each do |values, type|
13
- if [values].flatten.map { |value| value.to_s }.include? @value
13
+ if [values].flatten.map { |value| value }.include? @value
14
14
  label = @value
15
15
  background = type.to_sym
16
16
  next
@@ -21,6 +21,11 @@ module Avo
21
21
  @query = @authorization.apply_policy @resource.model_class
22
22
  end
23
23
 
24
+ # Remove default_scope for index view
25
+ if @resource.unscoped_queries_on_index
26
+ @query = @query.unscoped
27
+ end
28
+
24
29
  # Eager load the relations
25
30
  if @resource.includes.present?
26
31
  @query = @query.includes(*@resource.includes)
@@ -0,0 +1,65 @@
1
+ import { Controller } from 'stimulus'
2
+
3
+ export default class extends Controller {
4
+ static targets = ['select', 'type']
5
+
6
+ get selectedType() {
7
+ return this.selectTarget.value
8
+ }
9
+
10
+ connect() {
11
+ this.setValidNames()
12
+ this.changedType()
13
+ }
14
+
15
+ setValidNames() {
16
+ this.typeTargets.forEach((target) => {
17
+ const { type } = target.dataset
18
+ const select = target.querySelector('select')
19
+ const name = select.getAttribute('name')
20
+
21
+ select.setAttribute('valid-name', name)
22
+ if (this.selectedType !== type) {
23
+ select.selectedIndex = 0
24
+ }
25
+ })
26
+ }
27
+
28
+ changedType() {
29
+ this.hideAllTypeTargets()
30
+ this.enableType(this.selectTarget.value)
31
+ }
32
+
33
+ hideAllTypeTargets() {
34
+ this.typeTargets.forEach((target) => {
35
+ this.hideTarget(target)
36
+ this.invalidateTarget(target)
37
+ })
38
+ }
39
+
40
+ hideTarget(target) {
41
+ target.classList.add('hidden')
42
+ }
43
+
44
+ invalidateTarget(target) {
45
+ const select = target.querySelector('select')
46
+
47
+ select.setAttribute('name', '')
48
+ }
49
+
50
+ validateTarget(target) {
51
+ const select = target.querySelector('select')
52
+ const validName = select.getAttribute('valid-name')
53
+
54
+ select.setAttribute('name', validName)
55
+ }
56
+
57
+ enableType(type) {
58
+ const target = this.typeTargets.find((typeTarget) => typeTarget.dataset.type === type)
59
+
60
+ if (target) {
61
+ target.classList.remove('hidden')
62
+ this.validateTarget(target)
63
+ }
64
+ }
65
+ }
@@ -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
@@ -95,13 +96,14 @@ module Avo
95
96
  field.visible?
96
97
  end
97
98
  .select do |field|
98
- if field.respond_to?(:polymorphic_for) &&
99
- field.polymorphic_for.present? &&
100
- field.polymorphic_for.to_s != field.get_model["#{field.polymorphic_as}_type"].to_s
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
101
104
  next
102
105
  end
103
- if !field.respond_to?(:polymorphic_for) &&
104
- field.respond_to?(:foreign_key) &&
106
+ if field.respond_to?(:foreign_key) &&
105
107
  reflection.present? &&
106
108
  reflection.respond_to?(:foreign_key) &&
107
109
  reflection.foreign_key != field.foreign_key
@@ -225,28 +227,6 @@ module Avo
225
227
  self.class.context
226
228
  end
227
229
 
228
- def query_search(via_resource_name:, via_resource_id:, user:, query: "")
229
- # model_class = self.model
230
-
231
- db_query = AuthorizationService.apply_policy(user, model_class)
232
-
233
- if via_resource_name.present?
234
- related_model = App.get_resource_by_name(via_resource_name).model
235
-
236
- db_query = related_model.find(via_resource_id).public_send(plural_name.downcase)
237
- end
238
-
239
- new_query = []
240
-
241
- [search].flatten.each_with_index do |search_by, index|
242
- new_query.push "or" if index != 0
243
-
244
- new_query.push "text(#{search_by}) ILIKE '%#{query}%'"
245
- end
246
-
247
- db_query.where(new_query.join(" "))
248
- end
249
-
250
230
  def attached_file_fields
251
231
  get_field_definitions.select do |field|
252
232
  [Avo::Fields::FileField, Avo::Fields::FilesField].include? field.class
@@ -259,14 +239,17 @@ module Avo
259
239
  .reject do |field|
260
240
  field.computed
261
241
  end
262
- .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
263
246
 
264
247
  params.each do |key, value|
265
248
  field = fields_by_database_id[key]
266
249
 
267
250
  next unless field.present?
268
251
 
269
- model = field.fill_field model, key, value
252
+ model = field.fill_field model, key, value, params
270
253
  end
271
254
 
272
255
  model
@@ -304,19 +287,22 @@ module Avo
304
287
 
305
288
  # We will not overwrite any attributes that come pre-filled in the model.
306
289
  def hydrate_model_with_default_values
307
- default_values = get_fields.select do |field|
308
- !field.computed
309
- end
290
+ default_values = get_fields
291
+ .select do |field|
292
+ !field.computed
293
+ end
310
294
  .map do |field|
311
295
  id = field.id
312
296
  value = field.value
313
297
 
314
- if field.respond_to? :foreign_key
298
+ if field.type == "belongs_to"
315
299
  id = field.foreign_key.to_sym
316
300
 
317
301
  reflection = @model._reflections[@params[:via_relation]]
318
302
 
319
- 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
320
306
  value = @params[:via_resource_id]
321
307
  end
322
308
  end
@@ -325,8 +311,8 @@ module Avo
325
311
  end
326
312
  .to_h
327
313
  .select do |id, value|
328
- value.present?
329
- end
314
+ value.present?
315
+ end
330
316
 
331
317
  default_values.each do |id, value|
332
318
  if @model.send(id).nil?
@@ -116,7 +116,7 @@ module Avo
116
116
  final_value
117
117
  end
118
118
 
119
- def fill_field(model, key, value)
119
+ def fill_field(model, key, value, params)
120
120
  return model unless model.methods.include? key.to_sym
121
121
 
122
122
  model.send("#{key}=", value)
@@ -3,8 +3,8 @@ module Avo
3
3
  class BelongsToField < BaseField
4
4
  attr_reader :searchable
5
5
  attr_reader :polymorphic_as
6
- attr_reader :polymorphic_for
7
6
  attr_reader :relation_method
7
+ attr_reader :types
8
8
 
9
9
  def initialize(id, **args, &block)
10
10
  args[:placeholder] ||= I18n.t("avo.choose_an_option")
@@ -13,10 +13,8 @@ module Avo
13
13
 
14
14
  @searchable = args[:searchable] == true
15
15
  @polymorphic_as = args[:polymorphic_as]
16
- @polymorphic_for = args[:polymorphic_for]
16
+ @types = args[:types]
17
17
  @relation_method = name.to_s.parameterize.underscore
18
-
19
- hide_on(:edit, :new) if polymorphic_as.present?
20
18
  end
21
19
 
22
20
  def value
@@ -24,7 +22,7 @@ module Avo
24
22
  end
25
23
 
26
24
  def options
27
- target_resource.model_class.all.map do |model|
25
+ ::Avo::Services::AuthorizationService.apply_policy(user, target_resource.model_class).all.map do |model|
28
26
  {
29
27
  value: model.id,
30
28
  label: model.send(target_resource.class.title)
@@ -32,6 +30,12 @@ module Avo
32
30
  end
33
31
  end
34
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
+
35
39
  def database_value
36
40
  target_resource.id
37
41
  end
@@ -61,11 +65,49 @@ module Avo
61
65
  end
62
66
 
63
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
+
64
72
  foreign_key.to_sym
65
73
  end
66
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
+
67
103
  def target_resource
68
- return App.get_resource_by_model_name(polymorphic_for) if polymorphic_for.present?
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
69
111
 
70
112
  reflection_key = polymorphic_as || id
71
113