avo 2.3.1.pre.6 → 2.5.0

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -3
  3. data/app/assets/stylesheets/css/components/code.css +1 -0
  4. data/app/components/avo/edit/field_wrapper_component.html.erb +2 -2
  5. data/app/components/avo/edit/field_wrapper_component.rb +6 -1
  6. data/app/components/avo/fields/belongs_to_field/autocomplete_component.html.erb +3 -0
  7. data/app/components/avo/fields/belongs_to_field/autocomplete_component.rb +4 -0
  8. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +26 -27
  9. data/app/components/avo/filters_component.html.erb +3 -3
  10. data/app/components/avo/filters_component.rb +2 -1
  11. data/app/components/avo/paginator_component.html.erb +3 -3
  12. data/app/components/avo/paginator_component.rb +3 -3
  13. data/app/components/avo/profile_item_component.html.erb +1 -1
  14. data/app/components/avo/profile_item_component.rb +6 -1
  15. data/app/components/avo/sidebar_component.html.erb +5 -2
  16. data/app/components/avo/sidebar_profile_component.html.erb +1 -1
  17. data/app/components/avo/views/resource_index_component.html.erb +3 -3
  18. data/app/components/avo/views/resource_index_component.rb +10 -3
  19. data/app/controllers/avo/associations_controller.rb +6 -1
  20. data/app/controllers/avo/base_controller.rb +37 -9
  21. data/app/controllers/avo/debug_controller.rb +3 -0
  22. data/app/controllers/avo/search_controller.rb +22 -1
  23. data/app/helpers/avo/url_helpers.rb +3 -3
  24. data/app/javascript/js/controllers/fields/code_field_controller.js +0 -1
  25. data/app/javascript/js/controllers/filter_controller.js +20 -1
  26. data/app/javascript/js/controllers/multiple_select_filter_controller.js +3 -1
  27. data/app/javascript/js/controllers/search_controller.js +9 -2
  28. data/app/javascript/js/controllers/select_filter_controller.js +1 -1
  29. data/app/views/avo/base/_boolean_filter.html.erb +23 -28
  30. data/app/views/avo/base/_multiple_select_filter.html.erb +7 -14
  31. data/app/views/avo/base/_select_filter.html.erb +6 -10
  32. data/app/views/avo/base/_text_filter.html.erb +7 -11
  33. data/app/views/avo/base/index.html.erb +1 -0
  34. data/app/views/avo/debug/index.html.erb +3 -3
  35. data/app/views/avo/debug/report.html.erb +1 -1
  36. data/app/views/avo/partials/_footer.html.erb +1 -1
  37. data/app/views/avo/partials/_navbar.html.erb +4 -1
  38. data/app/views/avo/partials/_sidebar_extra.html.erb +2 -0
  39. data/db/factories.rb +5 -3
  40. data/lib/avo/app.rb +37 -12
  41. data/lib/avo/base_resource.rb +1 -0
  42. data/lib/avo/configuration.rb +2 -0
  43. data/lib/avo/engine.rb +13 -0
  44. data/lib/avo/fields/belongs_to_field.rb +11 -1
  45. data/lib/avo/fields/code_field.rb +1 -1
  46. data/lib/avo/fields/has_base_field.rb +2 -0
  47. data/lib/avo/filters/base_filter.rb +35 -2
  48. data/lib/avo/filters/boolean_filter.rb +12 -0
  49. data/lib/avo/filters/multiple_select_filter.rb +15 -0
  50. data/lib/avo/filters/text_filter.rb +4 -0
  51. data/lib/avo/hosts/association_scope_host.rb +8 -0
  52. data/lib/avo/licensing/h_q.rb +52 -15
  53. data/lib/avo/services/authorization_service.rb +8 -10
  54. data/lib/avo/version.rb +1 -1
  55. data/lib/generators/avo/eject_generator.rb +2 -3
  56. data/lib/generators/avo/templates/initializer/avo.tt +1 -0
  57. data/lib/generators/avo/templates/locales/avo.en.yml +2 -1
  58. data/lib/generators/avo/templates/locales/avo.nb-NO.yml +2 -1
  59. data/lib/generators/avo/templates/locales/avo.pt-BR.yml +2 -1
  60. data/lib/generators/avo/templates/locales/avo.ro.yml +2 -1
  61. data/lib/tasks/avo_tasks.rake +30 -0
  62. data/public/avo-assets/avo.css +11 -0
  63. data/public/avo-assets/avo.js +22 -22
  64. data/public/avo-assets/avo.js.map +2 -2
  65. metadata +5 -8
  66. data/app/assets/builds/avo.css +0 -8846
  67. data/app/assets/builds/avo.js +0 -423
  68. data/app/assets/builds/avo.js.map +0 -7
  69. data/app/mailers/avo/application_mailer.rb +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa5d2395db02e3b37f787b0680a4cae6748c99049f369d8e2a3db2fb6c2a2dd8
4
- data.tar.gz: 4de2e1dcc865e6ebbc693ddefd4e5407f9d6a6c60f3c1153deeb8d7dcc6ba17b
3
+ metadata.gz: a1c24962e7af4aae8d474872c00149aac873f23dbbfad3ddf4c8de53cc4c1f75
4
+ data.tar.gz: e356866b46a51297a576a79d77cdc45f29c3482409390a95257a36797f4e759d
5
5
  SHA512:
6
- metadata.gz: 498b612171a47529d988c8ba89ac0330013afef141a0a4d74df9d41fe60b7d00543777be08b0fa35bf1a5972c06f512a2be76510f4b5a9fd90d779c7fc90a2e8
7
- data.tar.gz: fefaff30ae62e0c5de60d64f3e5952bca7b1ae816d454613f3803ca268b2724505231dff2d9d9ce348a625a7ecf74fc9c669179fbbf03366f2c4c26a52c9b277
6
+ metadata.gz: 5b946132563c3180eefa6eee7417fadb6d85658ffef574560c748ca75da3353c277b978884de218701fedf1e2db732fe129418ae26effad8e4d272223b5aeb25
7
+ data.tar.gz: 25e18c2e1de5509adfe6a051e64195d5522bfdccceecf9208458d0c0603f3452f818759056c2f24fdaedaa89788bcf00a7e17574bde09265b1c2773fece5e152
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.3.1.pre.6)
4
+ avo (2.5.0)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -248,8 +248,6 @@ GEM
248
248
  nokogiri (1.13.4)
249
249
  mini_portile2 (~> 2.8.0)
250
250
  racc (~> 1.4)
251
- nokogiri (1.13.4-x86_64-linux)
252
- racc (~> 1.4)
253
251
  orm_adapter (0.5.0)
254
252
  pagy (5.10.1)
255
253
  activesupport
@@ -8,6 +8,7 @@
8
8
  .CodeMirror {
9
9
  height: var(--height) !important;
10
10
  min-height: auto;
11
+ padding: 0;
11
12
  }
12
13
 
13
14
  /* overlay CodeMirror fullscreen & Preview on small screens */
@@ -4,8 +4,8 @@
4
4
  <% if @model.present? and @model.errors.include? @field.id %>
5
5
  <div class="text-red-600 mt-2 text-sm"><%= @model.errors.full_messages_for(@field.id).to_sentence %></div>
6
6
  <% end %>
7
- <% if @field.help %>
8
- <div class="text-gray-600 mt-2 text-sm"><%== @field.help %></div>
7
+ <% if help.present? %>
8
+ <div class="text-gray-600 mt-2 text-sm"><%== help %></div>
9
9
  <% end %>
10
10
  </div>
11
11
  <% end %>
@@ -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: {}, label: nil, **args)
4
+ def initialize(field: nil, dash_if_blank: true, full_width: false, displayed_in_modal: false, form: nil, resource: {}, label: nil, help: nil, **args)
5
5
  @field = field
6
6
  @dash_if_blank = dash_if_blank
7
7
  @classes = args[:class].present? ? args[:class] : ""
@@ -12,5 +12,10 @@ class Avo::Edit::FieldWrapperComponent < ViewComponent::Base
12
12
  @model = resource.present? ? resource.model : nil
13
13
  @full_width = full_width
14
14
  @label = label
15
+ @help = help
16
+ end
17
+
18
+ def help
19
+ @help || @field.help
15
20
  end
16
21
  end
@@ -4,6 +4,9 @@
4
4
  data-search-resource="<%= @model_key %>"
5
5
  data-translation-keys='{"no_item_found": "<%= I18n.translate 'avo.no_item_found' %>"}'
6
6
  data-via-association="belongs_to"
7
+ data-via-association-id="<%= @field.id %>"
8
+ data-via-reflection-id="<%= @field.model.id %>"
9
+ data-via-reflection-class="<%= @field.model.class.to_s %>"
7
10
  ></div>
8
11
  <div class="relative w-full" autocomplete="off">
9
12
  <%= @form.text_field @foreign_key,
@@ -34,6 +34,10 @@ class Avo::Fields::BelongsToField::AutocompleteComponent < ViewComponent::Base
34
34
  result
35
35
  end
36
36
 
37
+ def reflection_class
38
+ has_polymorphic_association? ? polymorphic_class : @resource.model_class._reflections[@field.id.to_s].klass
39
+ end
40
+
37
41
  private
38
42
 
39
43
  def should_prefill?
@@ -1,20 +1,19 @@
1
- <%
2
- if is_polymorphic?
3
-
4
- # Set the model keys so we can pass them over
5
- model_keys = @field.types.map do |type|
6
- resource = Avo::App.get_resource_by_model_name(type.to_s)
7
- [type.to_s, resource.model_key]
8
- end.to_h
9
- %>
10
- <div class="divide-y"
1
+ <% if is_polymorphic? %>
2
+ <%
3
+ # Set the model keys so we can pass them over
4
+ model_keys = @field.types.map do |type|
5
+ resource = Avo::App.get_resource_by_model_name(type.to_s)
6
+ [type.to_s, resource.model_key]
7
+ end.to_h
8
+ %>
9
+ <div class="divide-y"
11
10
  data-controller="belongs-to-field"
12
11
  data-searchable="<%= @field.searchable %>"
13
12
  data-association="<%= @field.id %>"
14
13
  data-association-class="<%= @field&.target_resource&.model_class || nil %>"
15
14
  >
16
- <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
17
- <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
15
+ <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, help: @field.polymorphic_help || '' do %>
16
+ <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [type.to_s.underscore.humanize, type.to_s] },
18
17
  {
19
18
  value: @field.value,
20
19
  include_blank: @field.placeholder,
@@ -26,21 +25,21 @@
26
25
  'data-action': 'change->belongs-to-field#changeType'
27
26
  }
28
27
  %>
29
- <%
28
+ <%
30
29
  # If the select field is disabled, no value will be sent. It's how HTML works.
31
30
  # Thus the extra hidden field to actually send the related id to the server.
32
31
  if disabled %>
33
- <%= @form.hidden_field @field.type_input_foreign_key %>
32
+ <%= @form.hidden_field @field.type_input_foreign_key %>
33
+ <% end %>
34
34
  <% end %>
35
- <% end %>
36
- <% @field.types.each do |type| %>
37
- <div class="hidden"
35
+ <% @field.types.each do |type| %>
36
+ <div class="hidden"
38
37
  data-belongs-to-field-target="type"
39
38
  data-type="<%= type %>"
40
39
  >
41
- <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, label: type.to_s.underscore.humanize do %>
42
- <% if @field.searchable %>
43
- <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
40
+ <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, label: type.to_s.underscore.humanize do %>
41
+ <% if @field.searchable %>
42
+ <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
44
43
  field: @field,
45
44
  type: type,
46
45
  model_key: model_keys[type.to_s],
@@ -49,8 +48,8 @@
49
48
  disabled: disabled,
50
49
  polymorphic_record: polymorphic_record
51
50
  %>
52
- <% else %>
53
- <%= @form.select @field.id_input_foreign_key,
51
+ <% else %>
52
+ <%= @form.select @field.id_input_foreign_key,
54
53
  options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
55
54
  {
56
55
  value: @resource.model[@field.id_input_foreign_key].to_s,
@@ -61,17 +60,17 @@
61
60
  disabled: disabled
62
61
  }
63
62
  %>
64
- <%
63
+ <%
65
64
  # If the select field is disabled, no value will be sent. It's how HTML works.
66
65
  # Thus the extra hidden field to actually send the related id to the server.
67
66
  if disabled %>
68
67
  <%= @form.hidden_field @field.id_input_foreign_key %>
69
68
  <% end %>
69
+ <% end %>
70
70
  <% end %>
71
- <% end %>
72
- </div>
73
- <% end %>
74
- </div>
71
+ </div>
72
+ <% end %>
73
+ </div>
75
74
  <% else %>
76
75
  <%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
77
76
  <% if @field.searchable %>
@@ -9,12 +9,12 @@
9
9
  'data-action': 'click->toggle-panel#togglePanel',
10
10
  'data-tippy': 'tooltip' do %>
11
11
  <%= t 'avo.filters' %>
12
- <% if params[:filters].present? %>
13
- <span class="ml-1">(<%=JSON.parse(Base64.decode64(params[:filters])).count%> applied)</span>
12
+ <% if @applied_filters.present? %>
13
+ <span class="ml-1">(<%= @applied_filters.count %> applied)</span>
14
14
  <% end %>
15
15
  <% end %>
16
16
  <div
17
- class="absolute block inset-auto sm:right-0 top-full bg-white min-w-[300px] mt-2 z-20 shadow-modal rounded hidden divide-y divide-gray-300"
17
+ class="absolute block inset-auto sm:right-0 top-full bg-white min-w-[300px] mt-2 z-20 shadow-modal rounded divide-y divide-gray-300 <%= 'hidden' unless params[:keep_filters_panel_open] %>"
18
18
  data-toggle-panel-target="panel"
19
19
  >
20
20
  <% @filters.each do |filter| %>
@@ -3,9 +3,10 @@
3
3
  class Avo::FiltersComponent < ViewComponent::Base
4
4
  include Avo::ApplicationHelper
5
5
 
6
- def initialize(filters: [], resource: nil)
6
+ def initialize(filters: [], resource: nil, applied_filters: [])
7
7
  @filters = filters
8
8
  @resource = resource
9
+ @applied_filters = applied_filters
9
10
  end
10
11
 
11
12
  def render?
@@ -1,7 +1,7 @@
1
1
  <%
2
2
  per_page_options = [*Avo.configuration.per_page_steps, Avo.configuration.per_page.to_i, index_params[:per_page].to_i]
3
3
 
4
- if parent_resource.present?
4
+ if parent_model.present?
5
5
  per_page_options.prepend Avo.configuration.via_per_page
6
6
  end
7
7
 
@@ -24,8 +24,8 @@
24
24
  %> <%= t('avo.per_page').downcase %>
25
25
  </div>
26
26
  <% per_page_options.each do |option| %>
27
- <% if parent_resource.present? %>
28
- <%= link_to "Change to #{option} items per page", helpers.related_resources_path(parent_resource.model_class, resource.model_class, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
27
+ <% if parent_model.present? %>
28
+ <%= link_to "Change to #{option} items per page", helpers.related_resources_path(parent_model, parent_model, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
29
29
  <% else %>
30
30
  <%= link_to "Change to #{option} items per page", helpers.resources_path(resource: resource, per_page: option, keep_query_params: true), class: 'hidden', 'data-per-page-option': option, 'data-turbo-frame': turbo_frame %>
31
31
  <% end %>
@@ -5,13 +5,13 @@ class Avo::PaginatorComponent < ViewComponent::Base
5
5
  attr_reader :turbo_frame
6
6
  attr_reader :index_params
7
7
  attr_reader :resource
8
- attr_reader :parent_resource
8
+ attr_reader :parent_model
9
9
 
10
- def initialize(resource: nil, parent_resource: nil, pagy:, turbo_frame:, index_params:)
10
+ def initialize(resource: nil, parent_model: nil, pagy: nil, turbo_frame: nil, index_params: nil)
11
11
  @pagy = pagy
12
12
  @turbo_frame = turbo_frame
13
13
  @index_params = index_params
14
14
  @resource = resource
15
- @parent_resource = parent_resource
15
+ @parent_model = parent_model
16
16
  end
17
17
  end
@@ -1,3 +1,3 @@
1
- <%= link_to path, class: "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-gray-800 font-semibold hover:bg-blue-100 block px-4 py-1 w-full py-3 text-center rounded w-full", target: target do %>
1
+ <%= link_to path, class: "flex-1 flex items-center justify-center bg-white text-left cursor-pointer text-gray-800 font-semibold hover:bg-blue-100 block px-4 py-1 w-full py-3 text-center rounded w-full", target: target, title: title do %>
2
2
  <%= helpers.svg(icon, class: 'h-4 mr-1') if icon.present? %> <%= label %>
3
3
  <% end %>
@@ -7,11 +7,16 @@ class Avo::ProfileItemComponent < ViewComponent::Base
7
7
  attr_reader :active
8
8
  attr_reader :target
9
9
 
10
- def initialize(label: nil, icon: nil, path: nil, active: :inclusive, target: nil)
10
+ def initialize(label: nil, icon: nil, path: nil, active: :inclusive, target: nil, title: nil)
11
11
  @label = label
12
12
  @icon = icon
13
13
  @path = path
14
14
  @active = active
15
15
  @target = target
16
+ @title = title
17
+ end
18
+
19
+ def title
20
+ @title || @label
16
21
  end
17
22
  end
@@ -1,4 +1,7 @@
1
- <div class="fixed z-[60] application-sidebar hidden lg:flex h-full bg-white text-white w-64 border-r" data-mobile-target="sidebar">
1
+ <div
2
+ class="fixed z-[60] application-sidebar hidden lg:flex h-full bg-white text-white w-64 border-r <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
3
+ data-mobile-target="sidebar"
4
+ >
2
5
  <div class="flex flex-col w-full h-full">
3
6
  <div class="flex justify-between">
4
7
  <%= render partial: "avo/partials/logo" %>
@@ -10,7 +13,7 @@
10
13
  <div class="space-y-6 mb-4">
11
14
  <%= render Avo::Sidebar::LinkComponent.new label: 'Get started', path: helpers.avo.root_path, active: :exclusive if Rails.env.development? && Avo.configuration.home_path.nil? %>
12
15
 
13
- <% if Avo::App.license.has_with_trial(:menu_editor) && Avo.configuration.main_menu.present? %>
16
+ <% if Avo::App.has_main_menu? %>
14
17
  <div class="text-black">
15
18
  <% Avo::App.main_menu.items.each do |item| %>
16
19
  <%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
@@ -25,7 +25,7 @@
25
25
  class="hidden absolute flex flex-col inset-auto right-0 -mt-12 bg-white rounded min-w-[200px] shadow-context -translate-y-full"
26
26
  data-toggle-panel-target="panel"
27
27
  >
28
- <% if Avo::App.license.has_with_trial(:menu_editor) && Avo.configuration.profile_menu.present? %>
28
+ <% if Avo::App.has_profile_menu? %>
29
29
  <div class="text-black space-y-4">
30
30
  <% Avo::App.profile_menu.items.each do |item| %>
31
31
  <% if item.is_a? Avo::Menu::Link %>
@@ -30,7 +30,7 @@
30
30
  <% end %>
31
31
  <% if @filters.present? || available_view_types.count > 1 %>
32
32
  <div class="justify-self-end flex justify-start xs:justify-end items-center px-4 space-x-3">
33
- <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource %>
33
+ <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource, applied_filters: @applied_filters %>
34
34
  <%= render partial: 'avo/partials/view_toggle_button', locals: { available_view_types: available_view_types, view_type: view_type, turbo_frame: @turbo_frame } if available_view_types.count > 1 %>
35
35
  </div>
36
36
  <% end %>
@@ -51,7 +51,7 @@
51
51
  <% if view_type.to_sym == :table %>
52
52
  <% if @models.present? %>
53
53
  <div class="mt-8">
54
- <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_resource: @parent_resource %>
54
+ <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
55
55
  </div>
56
56
  <% end %>
57
57
  <% end %>
@@ -59,7 +59,7 @@
59
59
  <% if view_type.to_sym == :grid %>
60
60
  <%= render Avo::Index::ResourceGridComponent.new(resources: @resources, resource: @resource, reflection: @reflection, parent_model: @parent_model) %>
61
61
  <div class="mt-14">
62
- <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_resource: @parent_resource %>
62
+ <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: @turbo_frame || 'none', index_params: @index_params, resource: @resource, parent_model: @parent_model %>
63
63
  </div>
64
64
  <% end %>
65
65
  <% end %>
@@ -14,7 +14,8 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
14
14
  actions: [],
15
15
  reflection: nil,
16
16
  turbo_frame: "",
17
- parent_model: nil
17
+ parent_model: nil,
18
+ applied_filters: []
18
19
  )
19
20
  @resource = resource
20
21
  @resources = resources
@@ -26,6 +27,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
26
27
  @reflection = reflection
27
28
  @turbo_frame = turbo_frame
28
29
  @parent_model = parent_model
30
+ @applied_filters = applied_filters
29
31
  end
30
32
 
31
33
  def title
@@ -90,7 +92,7 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
90
92
  if @reflection.present?
91
93
  args = {
92
94
  via_relation_class: reflection_model_class,
93
- via_resource_id: @parent_model.id,
95
+ via_resource_id: @parent_model.id
94
96
  }
95
97
 
96
98
  if @reflection.is_a? ActiveRecord::Reflection::ThroughReflection
@@ -126,7 +128,12 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
126
128
  end
127
129
 
128
130
  def description
129
- return if @reflection.present?
131
+ # If this is a has many association, the user can pass a description to be shown just for this association.
132
+ if @reflection.present?
133
+ return field.description if field.present? && field.description
134
+
135
+ return
136
+ end
130
137
 
131
138
  @resource.resource_description
132
139
  end
@@ -21,7 +21,7 @@ module Avo
21
21
  @association_field = @parent_resource.get_field params[:related_name]
22
22
 
23
23
  if @association_field.present? && @association_field.scope.present?
24
- @query = @query.instance_exec(&@association_field.scope)
24
+ @query = Avo::Hosts::AssociationScopeHost.new(block: @association_field.scope, query: @query, parent: @parent_model).handle
25
25
  end
26
26
 
27
27
  super
@@ -45,6 +45,11 @@ module Avo
45
45
  if @field.present? && !@field.searchable
46
46
  query = @authorization.apply_policy @attachment_class
47
47
 
48
+ # Add the association scope to the query scope
49
+ if @field.scope.present?
50
+ query = Avo::Hosts::AssociationScopeHost.new(block: @field.scope, query: query, parent: @model).handle
51
+ end
52
+
48
53
  @options = query.all.map do |model|
49
54
  [model.send(@attachment_resource.class.title), model.id]
50
55
  end
@@ -5,6 +5,7 @@ module Avo
5
5
  before_action :set_resource_name
6
6
  before_action :set_resource
7
7
  before_action :hydrate_resource
8
+ before_action :set_applied_filters, only: :index
8
9
  before_action :set_model, only: [:show, :edit, :destroy, :update, :order]
9
10
  before_action :set_model_to_fill
10
11
  before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update]
@@ -55,8 +56,8 @@ module Avo
55
56
  end
56
57
  end
57
58
 
58
- # Apply filters
59
- applied_filters.each do |filter_class, filter_value|
59
+ # Apply filters to the current query
60
+ filters_to_be_applied.each do |filter_class, filter_value|
60
61
  @query = filter_class.safe_constantize.new.apply_query request, @query, filter_value
61
62
  end
62
63
 
@@ -308,28 +309,55 @@ module Avo
308
309
  .select { |action| action.visible_in_view }
309
310
  end
310
311
 
311
- def applied_filters
312
- if params[:filters].present?
313
- return JSON.parse(Base64.decode64(params[:filters]))
314
- end
312
+ def set_applied_filters
313
+ @applied_filters = Avo::Filters::BaseFilter.decode_filters(params[Avo::Filters::BaseFilter::PARAM_KEY])
314
+
315
+ # Some filters react to others and will have to be merged into this
316
+ @applied_filters = @applied_filters.merge reactive_filters
317
+ rescue
318
+ @applied_filters = {}
319
+ end
320
+
321
+ def reactive_filters
322
+ filter_reactions = {}
323
+
324
+ # Go through all filters
325
+ @resource.get_filters
326
+ .select do |filter_class|
327
+ filter_class.instance_methods(false).include? :react
328
+ end
329
+ .each do |filter_class|
330
+ # Run the react method if it's present
331
+ reaction = filter_class.new.react
332
+
333
+ next if reaction.nil?
334
+
335
+ filter_reactions[filter_class.to_s] = filter_class.new.react
336
+ end
337
+
338
+ filter_reactions
339
+ end
315
340
 
341
+ # Get the default state of the filters and override with the user applied filters
342
+ def filters_to_be_applied
316
343
  filter_defaults = {}
317
344
 
318
345
  @resource.get_filters.each do |filter_class|
319
346
  filter = filter_class.new
320
347
 
321
- if filter.default.present?
348
+ unless filter.default.nil?
322
349
  filter_defaults[filter_class.to_s] = filter.default
323
350
  end
324
351
  end
325
352
 
326
- filter_defaults
353
+ filter_defaults.merge(@applied_filters)
327
354
  end
328
355
 
356
+ # Caching these so we know when the filters have changed so we reset the pagination
329
357
  def cache_applied_filters
330
358
  ::Avo::App.cache_store.delete applied_filters_cache_key if params[:filters].nil?
331
359
 
332
- ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 7.days)
360
+ ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
333
361
  end
334
362
 
335
363
  def reset_pagination_if_filters_changed
@@ -5,6 +5,9 @@ module Avo
5
5
  def index
6
6
  end
7
7
 
8
+ def report
9
+ end
10
+
8
11
  def refresh_license
9
12
  license = Licensing::LicenseManager.refresh_license request
10
13
 
@@ -44,7 +44,23 @@ module Avo
44
44
  end
45
45
 
46
46
  def search_resource(resource)
47
- results = apply_search_metadata(resource.search_query.call(params: params).limit(8), resource)
47
+ query = resource.search_query.call(params: params).limit(8)
48
+
49
+ # Figure oute if this is a belongs_to search
50
+ if params[:via_reflection_class].present? && params[:via_reflection_id].present?
51
+ # Fetch the field
52
+ field = belongs_to_field
53
+
54
+ if field.scope.present?
55
+ # Fetch the parent
56
+ parent = params[:via_reflection_class].safe_constantize.find params[:via_reflection_id]
57
+
58
+ # Add to the query
59
+ query = Avo::Hosts::AssociationScopeHost.new(block: belongs_to_field.scope, query: query, parent: parent).handle
60
+ end
61
+ end
62
+
63
+ results = apply_search_metadata(query, resource)
48
64
 
49
65
  result_object = {
50
66
  header: resource.name.pluralize,
@@ -75,5 +91,10 @@ module Avo
75
91
  result
76
92
  end
77
93
  end
94
+
95
+ def belongs_to_field
96
+ fields = ::Avo::App.get_resource_by_model_name(params[:via_reflection_class]).get_field_definitions
97
+ fields.find { |f| f.id.to_s == params[:via_association_id] }
98
+ end
78
99
  end
79
100
  end
@@ -59,11 +59,11 @@ module Avo
59
59
 
60
60
  def related_resources_path(
61
61
  parent_model,
62
- model,
62
+ record,
63
63
  keep_query_params: false,
64
64
  **args
65
65
  )
66
- return if model.nil?
66
+ return if record.nil?
67
67
 
68
68
  existing_params = {}
69
69
 
@@ -75,7 +75,7 @@ module Avo
75
75
  rescue
76
76
  end
77
77
 
78
- avo.resources_associations_index_path(@parent_resource.model_class.model_name.route_key, @parent_resource.model.id, **existing_params, **args )
78
+ avo.resources_associations_index_path(parent_model.model_name.route_key, record.id, **existing_params, **args)
79
79
  end
80
80
 
81
81
  def order_up_resource_path(model:, resource:, **args)
@@ -1,5 +1,4 @@
1
1
  import 'codemirror/mode/css/css'
2
-
3
2
  import 'codemirror/mode/dockerfile/dockerfile'
4
3
  import 'codemirror/mode/htmlmixed/htmlmixed'
5
4
  import 'codemirror/mode/javascript/javascript'
@@ -4,6 +4,10 @@ import URI from 'urijs'
4
4
  export default class extends Controller {
5
5
  static targets = ['urlRedirect']
6
6
 
7
+ static values = {
8
+ keepFiltersPanelOpen: Boolean,
9
+ }
10
+
7
11
  // eslint-disable-next-line class-methods-use-this
8
12
  uriParams() {
9
13
  return URI(window.location.toString()).query(true)
@@ -38,18 +42,22 @@ export default class extends Controller {
38
42
  const value = this.getFilterValue()
39
43
  const filterClass = this.getFilterClass()
40
44
 
45
+ // Get the `filters` param for all params
41
46
  let filters = this.uriParams()[this.uriParam('filters')]
42
47
 
48
+ // Decode the filters
43
49
  if (filters) {
44
50
  filters = JSON.parse(this.b64DecodeUnicode(filters))
45
51
  } else {
46
52
  filters = {}
47
53
  }
48
54
 
55
+ // Get the values for this particular filter
49
56
  filters[filterClass] = value
50
57
 
51
58
  const filtered = Object.keys(filters)
52
- .filter((key) => filters[key] !== '')
59
+ // Filter out the filters without a value
60
+ .filter((key) => filters[key] !== null)
53
61
  .reduce((obj, key) => {
54
62
  obj[key] = filters[key]
55
63
 
@@ -58,16 +66,27 @@ export default class extends Controller {
58
66
 
59
67
  let encodedFilters
60
68
 
69
+ // Encode the filters and their values
61
70
  if (filtered && Object.keys(filtered).length > 0) {
62
71
  encodedFilters = this.b64EncodeUnicode(JSON.stringify(filtered))
63
72
  }
64
73
 
74
+ this.navigateToURLWithFilters(encodedFilters)
75
+ }
76
+
77
+ navigateToURLWithFilters(encodedFilters) {
78
+ // Create a new URI with them
65
79
  const url = new URI(this.urlRedirectTarget.href)
66
80
 
67
81
  const query = {
68
82
  ...url.query(true),
69
83
  }
70
84
 
85
+ if (this.keepFiltersPanelOpenValue) {
86
+ // eslint-disable-next-line camelcase
87
+ query.keep_filters_panel_open = this.keepFiltersPanelOpenValue
88
+ }
89
+
71
90
  if (encodedFilters) {
72
91
  query.filters = encodedFilters
73
92
  } else {
@@ -4,7 +4,9 @@ export default class extends BaseFilterController {
4
4
  static targets = ['selector']
5
5
 
6
6
  getFilterValue() {
7
- return Array.from(this.selectorTarget.selectedOptions).map(({ value }) => value)
7
+ const filterValue = Array.from(this.selectorTarget.selectedOptions).map(({ value }) => value)
8
+
9
+ return filterValue.length === 0 ? null : filterValue
8
10
  }
9
11
 
10
12
  getFilterClass() {