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
@@ -68,8 +68,15 @@ export default class extends Controller {
68
68
  }
69
69
 
70
70
  if (this.isBelongsToSearch) {
71
- // eslint-disable-next-line camelcase
72
- params = { ...params, via_association: this.dataset.viaAssociation }
71
+ params = {
72
+ ...params,
73
+ // eslint-disable-next-line camelcase
74
+ via_association_id: this.dataset.viaAssociationId,
75
+ // eslint-disable-next-line camelcase
76
+ via_reflection_class: this.dataset.viaReflectionClass,
77
+ // eslint-disable-next-line camelcase
78
+ via_reflection_id: this.dataset.viaReflectionId,
79
+ }
73
80
  }
74
81
 
75
82
  return url.segment(segments).search(params).toString()
@@ -4,7 +4,7 @@ export default class extends BaseFilterController {
4
4
  static targets = ['selector']
5
5
 
6
6
  getFilterValue() {
7
- return this.selectorTarget.value
7
+ return this.selectorTarget.value === '' ? null : this.selectorTarget.value
8
8
  }
9
9
 
10
10
  getFilterClass() {
@@ -1,33 +1,28 @@
1
- <%
2
- begin
3
- decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
4
- set_value = decoded_filters_param[filter.class.to_s]
5
- rescue => exception
6
- if filter.default.present?
7
- set_value = filter.default.stringify_keys
8
- else
9
- set_value = {}
10
- end
11
- end
12
- set_value = {} if set_value.nil?
13
- %>
14
- <div data-controller="boolean-filter" data-filter-name="<%= filter.name %>">
1
+ <div
2
+ data-controller="boolean-filter"
3
+ data-filter-name="<%= filter.name %>"
4
+ data-boolean-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
+ >
15
6
  <%= filter_wrapper name: filter.name do %>
16
7
  <div class="flex items-center">
17
- <div class="space-y-2">
18
- <% filter.options.each do |value, label| %>
19
- <label class="flex items-center text-gray-700 text-sm">
20
- <%= check_box_tag filter.id, value, set_value[value.to_s],
21
- class: 'mr-2 text-lg h-4 w-4',
22
- id: "avo_filters_#{filter.id.parameterize.underscore}",
23
- 'data-filter-class': filter.class,
24
- 'data-boolean-filter-target': 'option',
25
- 'data-action': 'input->boolean-filter#changeFilter'
26
- %>
27
- <%= label %>
28
- </label>
29
- <% end %>
30
- </div>
8
+ <% if filter.options.empty? %>
9
+ <div class="text-sm text-gray-600"><%= filter.class.empty_message %></div>
10
+ <% else %>
11
+ <div class="space-y-2">
12
+ <% filter.options.each do |value, label| %>
13
+ <label class="flex items-center text-gray-700 text-sm">
14
+ <%= check_box_tag filter.id, value, filter.selected_value(value.to_s, @applied_filters),
15
+ class: 'mr-2 text-lg h-4 w-4',
16
+ id: "avo_filters_#{filter.id.parameterize.underscore}",
17
+ 'data-filter-class': filter.class,
18
+ 'data-boolean-filter-target': 'option',
19
+ 'data-action': 'input->boolean-filter#changeFilter'
20
+ %>
21
+ <%= label %>
22
+ </label>
23
+ <% end %>
24
+ </div>
25
+ <% end %>
31
26
  <%= link_to 'url_redirect', request.url, data: { 'boolean-filter-target': 'urlRedirect', 'turbo-frame': params[:turbo_frame] }, style: 'hidden', class: 'hidden' %>
32
27
  </div>
33
28
  <% end %>
@@ -1,17 +1,10 @@
1
- <%
2
- set_value = filter.default.present? ? filter.default.select { |key, value| value }.keys.map(&:to_sym) : {}
3
-
4
- begin
5
- decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
6
- if decoded_filters_param[filter.class.to_s].present?
7
- set_value = decoded_filters_param[filter.class.to_s]
8
- end
9
- rescue
10
- end
11
- %>
12
- <div data-controller="multiple-select-filter" data-filter-name="<%= filter.name %>">
1
+ <div
2
+ data-controller="multiple-select-filter"
3
+ data-filter-name="<%= filter.name %>"
4
+ data-multiple-select-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
+ >
13
6
  <%= filter_wrapper name: filter.name do %>
14
- <%= select_tag filter.id, options_for_select(filter.options.invert, set_value),
7
+ <%= select_tag filter.id, options_for_select(filter.options.invert, filter.selected_value(@applied_filters)),
15
8
  class: input_classes('w-full mb-0'),
16
9
  multiple: true,
17
10
  id: "avo_filters_#{filter.id.parameterize.underscore}",
@@ -19,7 +12,7 @@
19
12
  'data-multiple-select-filter-target': 'selector'
20
13
  %>
21
14
  <div class="flex justify-end">
22
- <%= a_button class: 'mt-4', color: :blue, size: :sm, data: { action: "multiple-select-filter#changeFilter" } do %>
15
+ <%= a_button class: 'mt-4', color: :blue, size: :xs, data: { action: "multiple-select-filter#changeFilter" } do %>
23
16
  Filter by <%=filter.name %>
24
17
  <% end %>
25
18
  </div>
@@ -1,14 +1,10 @@
1
- <%
2
- begin
3
- decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
4
- set_value = decoded_filters_param[filter.class.to_s]
5
- rescue => exception
6
- set_value = filter.default
7
- end
8
- %>
9
- <div data-controller="select-filter" data-filter-name="<%= filter.name %>">
1
+ <div
2
+ data-controller="select-filter"
3
+ data-filter-name="<%= filter.name %>"
4
+ data-select-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
+ >
10
6
  <%= filter_wrapper name: filter.name do %>
11
- <%= select_tag filter.id, options_for_select(filter.options.invert, set_value),
7
+ <%= select_tag filter.id, options_for_select(filter.options.invert, filter.applied_or_default_value(@applied_filters)),
12
8
  class: input_classes('w-full mb-0'),
13
9
  include_blank: '—',
14
10
  id: "avo_filters_#{filter.id.parameterize.underscore}",
@@ -1,14 +1,10 @@
1
- <%
2
- begin
3
- decoded_filters_param = JSON.parse(Base64.decode64(params[:filters]))
4
- set_value = decoded_filters_param[filter.class.to_s]
5
- rescue => exception
6
- set_value = filter.default
7
- end
8
- %>
9
- <div data-controller="text-filter" data-filter-name="<%= filter.name %>">
1
+ <div
2
+ data-controller="text-filter"
3
+ data-filter-name="<%= filter.name %>"
4
+ data-text-filter-keep-filters-panel-open-value="<%= @resource.keep_filters_panel_open %>"
5
+ >
10
6
  <%= filter_wrapper name: filter.name do %>
11
- <%= text_field_tag filter.id, set_value,
7
+ <%= text_field_tag filter.id, filter.applied_or_default_value(@applied_filters),
12
8
  class: input_classes('w-full mb-0'),
13
9
  id: "avo_filters_#{filter.id.parameterize.underscore}",
14
10
  'data-filter-class': filter.class.to_s,
@@ -16,7 +12,7 @@
16
12
  'data-action': 'keypress->text-filter#tryToSubmit'
17
13
  %>
18
14
  <div class="flex justify-end">
19
- <%= a_button class: 'mt-4', color: :blue, data: { action: "text-filter#changeFilter" }, size: :sm do %>
15
+ <%= a_button class: 'mt-4', color: :blue, data: { action: "text-filter#changeFilter" }, size: :xs do %>
20
16
  <%= filter.button_label || "Filter by #{filter.name}" %>
21
17
  <% end %>
22
18
  </div>
@@ -10,6 +10,7 @@
10
10
  reflection: @reflection,
11
11
  turbo_frame: params[:turbo_frame],
12
12
  parent_model: @parent_model,
13
+ applied_filters: @applied_filters,
13
14
  )
14
15
  %>
15
16
  <% end %>
@@ -21,7 +21,7 @@
21
21
  <div class="grid gap-4 sm:grid-cols-3">
22
22
  <div class="relative flex flex-col bg-white rounded shadow-panel p-4 space-y-4 h-full col-span-1">
23
23
  <div class="font-semibold">License info</div>
24
- <div class="flex flex-col justify-between flex-1">
24
+ <div class="flex flex-col flex-1">
25
25
  <div>
26
26
  <div class="text-xl font-semibold"><%= license.name %></div>
27
27
  <% if license.response['reason'] %>
@@ -57,7 +57,7 @@
57
57
  <div class="flex justify-end mt-4">
58
58
  <%= a_button style: :outline,
59
59
  color: :blue,
60
- url: '/admin/avo_private/debug/refresh_license',
60
+ url: "#{root_path}avo_private/debug/refresh_license",
61
61
  method: :post,
62
62
  loading: true,
63
63
  icon: 'heroicons/outline/refresh' do %>
@@ -67,7 +67,7 @@
67
67
  </div>
68
68
  </div>
69
69
  <div class="relative bg-white rounded shadow-panel p-4 space-y-4 col-span-2">
70
- <turbo-frame id="debug-report" src="/admin/avo_private/debug/report" target="_top" class="block">
70
+ <turbo-frame id="debug-report" src="<%= root_path %>avo_private/debug/report" target="_top" class="block">
71
71
  Loading...
72
72
  </turbo-frame>
73
73
  </div>
@@ -1,5 +1,5 @@
1
1
  <turbo-frame id="debug-report">
2
- <div class="font-semibold">Debug report</div>
2
+ <div class="font-semibold">Debug report <small class="font-normal">(don't post this anywhere public)</small></div>
3
3
  <div>
4
4
  <% if defined?(ap) %>
5
5
  <%== ap Avo::App.debug_report(request) %>
@@ -1,3 +1,3 @@
1
- <div class="text-center text-sm text-gray-700">
1
+ <div class="text-center text-sm text-gray-700 <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>">
2
2
  <a href="https://avohq.io/" target="_blank">Avo</a> · &copy; <%= Date.today.year %> AvoHQ · <span title="<%= Avo::App.license.valid ? 'valid' : 'invalid'%> <%= Avo::App.license.id %> license">v<%= Avo::VERSION %></span>
3
3
  </div>
@@ -1,4 +1,7 @@
1
- <div class="relative bg-white p-2 w-full flex flex-shrink-0 items-center z-50 px-4 lg:px-8 border-b space-x-4 lg:space-x-0 min-h-[4rem]" v-if="layout !== 'blank'">
1
+ <div
2
+ class="relative bg-white p-2 w-full flex flex-shrink-0 items-center z-50 px-4 lg:px-8 border-b space-x-4 lg:space-x-0 min-h-[4rem] <%= 'print:hidden' if Avo.configuration.hide_layout_when_printing %>"
3
+ v-if="layout !== 'blank'"
4
+ >
2
5
  <%= a_button class: 'lg:hidden', icon: 'menu', data: { action: 'click->mobile#toggleSidebar' } %>
3
6
  <div class="flex-1 flex items-center justify-between lg:justify-start space-x-8">
4
7
  <div class="m-0">
@@ -0,0 +1,2 @@
1
+ <%# Example link item below %>
2
+ <%#= render Avo::Sidebar::LinkComponent.new label: 'Label', path: '/path' %>
data/db/factories.rb CHANGED
@@ -25,7 +25,7 @@ FactoryBot.define do
25
25
  Time.now - rand(10...365).days
26
26
  end
27
27
  end
28
- status { 0 }
28
+ status { ::Post.statuses.keys.sample }
29
29
  end
30
30
 
31
31
  factory :project do
@@ -42,11 +42,11 @@ FactoryBot.define do
42
42
  end
43
43
 
44
44
  factory :comment do
45
- body { Faker::Lorem.paragraphs(number: rand(4...10)).join(' ') }
45
+ body { Faker::Lorem.paragraphs(number: rand(4...10)).join(" ") }
46
46
  end
47
47
 
48
48
  factory :review do
49
- body { Faker::Lorem.paragraphs(number: rand(4...10)).join(' ') }
49
+ body { Faker::Lorem.paragraphs(number: rand(4...10)).join(" ") }
50
50
  end
51
51
 
52
52
  factory :person do
@@ -64,6 +64,8 @@ FactoryBot.define do
64
64
 
65
65
  factory :course do
66
66
  name { Faker::Educator.unique.course_name }
67
+ country { Course.countries.sample }
68
+ city { Course.cities.stringify_keys[country].sample }
67
69
  end
68
70
 
69
71
  factory :course_link, class: "Course::Link" do
data/lib/avo/app.rb CHANGED
@@ -104,37 +104,62 @@ module Avo
104
104
  end
105
105
  end
106
106
 
107
+ def has_main_menu?
108
+ return false if Avo::App.license.lacks_with_trial(:menu_editor)
109
+ return false if Avo.configuration.main_menu.nil?
110
+
111
+ true
112
+ end
113
+
114
+ def has_profile_menu?
115
+ return false if Avo::App.license.lacks_with_trial(:menu_editor)
116
+ return false if Avo.configuration.profile_menu.nil?
117
+
118
+ true
119
+ end
120
+
107
121
  def main_menu
108
- return [] if Avo::App.license.lacks_with_trial(:menu_builder)
109
- return [] if Avo.configuration.main_menu.nil?
122
+ # Return empty menu if the app doesn't have the profile menu configured
123
+ return Avo::Menu::Builder.new.build unless has_main_menu?
110
124
 
111
125
  Avo::Menu::Builder.parse_menu(&Avo.configuration.main_menu)
112
126
  end
113
127
 
114
128
  def profile_menu
115
- return [] if Avo::App.license.lacks_with_trial(:menu_builder)
116
- return [] if Avo.configuration.profile_menu.nil?
129
+ # Return empty menu if the app doesn't have the profile menu configured
130
+ return Avo::Menu::Builder.new.build unless has_profile_menu?
117
131
 
118
132
  Avo::Menu::Builder.parse_menu(&Avo.configuration.profile_menu)
119
133
  end
120
134
 
121
- def debug_report(request)
135
+ def debug_report(request = nil)
122
136
  payload = {}
137
+
123
138
  hq = Avo::Licensing::HQ.new(request)
124
139
 
125
- payload[:hq_payload] = hq.payload
126
- payload[:license_id] = Avo::App.license.id
127
- payload[:license_valid] = Avo::App.license.valid?
128
- payload[:license_payload] = Avo::App.license.payload
129
- payload[:license_response] = Avo::App.license.response
130
- payload[:cache_store] = self.cache_store.class.to_s
131
- payload[:avo_metadata] = hq.avo_metadata
140
+ payload[:thread_count] = get_thread_count
141
+ payload[:hq_payload] = hq&.payload
142
+ payload[:license_id] = Avo::App&.license&.id
143
+ payload[:license_valid] = Avo::App&.license&.valid?
144
+ payload[:license_payload] = Avo::App&.license&.payload
145
+ payload[:license_response] = Avo::App&.license&.response
146
+ payload[:license_abilities] = Avo::App&.license&.abilities
147
+ payload[:cache_store] = self.cache_store&.class&.to_s
148
+ payload[:avo_metadata] = hq&.avo_metadata
132
149
  payload[:app_timezone] = Time.now.zone
150
+ payload[:cache_key] = Avo::Licensing::HQ.cache_key
151
+ payload[:cache_key_contents] = hq&.cached_response
133
152
 
134
153
  payload
135
154
  rescue => e
136
155
  e
137
156
  end
157
+
158
+ def get_thread_count
159
+ Thread.list.select {|thread| thread.status == "run"}.count
160
+ rescue => e
161
+ e
162
+ end
138
163
  end
139
164
  end
140
165
  end
@@ -44,6 +44,7 @@ module Avo
44
44
  class_attribute :after_update_path, default: :show
45
45
  class_attribute :invalid_fields
46
46
  class_attribute :record_selector, default: true
47
+ class_attribute :keep_filters_panel_open, default: false
47
48
 
48
49
  class << self
49
50
  delegate :t, to: ::I18n
@@ -20,6 +20,7 @@ module Avo
20
20
  attr_accessor :cache_resources_on_index_view
21
21
  attr_accessor :context
22
22
  attr_accessor :display_breadcrumbs
23
+ attr_accessor :hide_layout_when_printing
23
24
  attr_accessor :initial_breadcrumbs
24
25
  attr_accessor :home_path
25
26
  attr_accessor :search_debounce
@@ -64,6 +65,7 @@ module Avo
64
65
  add_breadcrumb I18n.t("avo.home").humanize, avo.root_path
65
66
  }
66
67
  @display_breadcrumbs = true
68
+ @hide_layout_when_printing = false
67
69
  @home_path = nil
68
70
  @search_debounce = 300
69
71
  @view_component_path = "app/components"
data/lib/avo/engine.rb CHANGED
@@ -63,5 +63,18 @@ module Avo
63
63
  config.generators do |g|
64
64
  g.test_framework :rspec, view_specs: false
65
65
  end
66
+
67
+ # After deploy we want to make sure the license response is being cleared.
68
+ # We need a fresh license response.
69
+ # This is disabled in development because the initialization process might be triggered more than once.
70
+ config.after_initialize do
71
+ unless Rails.env.development?
72
+ begin
73
+ Licensing::HQ.new.clear_response
74
+ rescue => exception
75
+ puts "Failed to clear Avo HQ response: #{e.message}"
76
+ end
77
+ end
78
+ end
66
79
  end
67
80
  end
@@ -62,6 +62,8 @@ module Avo
62
62
  attr_reader :relation_method
63
63
  attr_reader :types # for Polymorphic associations
64
64
  attr_reader :allow_via_detaching
65
+ attr_reader :scope
66
+ attr_reader :polymorphic_help
65
67
 
66
68
  def initialize(id, **args, &block)
67
69
  args[:placeholder] ||= I18n.t("avo.choose_an_option")
@@ -73,6 +75,8 @@ module Avo
73
75
  @types = args[:types]
74
76
  @relation_method = id.to_s.parameterize.underscore
75
77
  @allow_via_detaching = args[:allow_via_detaching] == true
78
+ @scope = args[:scope]
79
+ @polymorphic_help = args[:polymorphic_help]
76
80
  end
77
81
 
78
82
  def searchable
@@ -111,7 +115,13 @@ module Avo
111
115
  resource = target_resource
112
116
  resource = App.get_resource_by_model_name model if model.present?
113
117
 
114
- ::Avo::Services::AuthorizationService.apply_policy(user, resource.class.query_scope).all.map do |model|
118
+ query = Avo::Services::AuthorizationService.apply_policy(user, resource.class.query_scope)
119
+
120
+ if scope.present?
121
+ query = Avo::Hosts::AssociationScopeHost.new(block: scope, query: query, parent: get_model).handle
122
+ end
123
+
124
+ query.all.map do |model|
115
125
  [model.send(resource.class.title), model.id]
116
126
  end
117
127
  end
@@ -14,7 +14,7 @@ module Avo
14
14
  super(id, **args, &block)
15
15
 
16
16
  @language = args[:language].present? ? args[:language].to_s : "javascript"
17
- @theme = args[:theme].present? ? args[:theme].to_s : "material-darker"
17
+ @theme = args[:theme].present? ? args[:theme].to_s : "default"
18
18
  @height = args[:height].present? ? args[:height].to_s : "auto"
19
19
  @tab_size = args[:tab_size].present? ? args[:tab_size] : 2
20
20
  @indent_with_tabs = args[:indent_with_tabs].present? ? args[:indent_with_tabs] : false
@@ -3,6 +3,7 @@ module Avo
3
3
  class HasBaseField < BaseField
4
4
  attr_accessor :display
5
5
  attr_accessor :scope
6
+ attr_accessor :description
6
7
 
7
8
  def initialize(id, **args, &block)
8
9
  super(id, **args, &block)
@@ -10,6 +11,7 @@ module Avo
10
11
  @scope = args[:scope].present? ? args[:scope] : nil
11
12
  @display = args[:display].present? ? args[:display] : :show
12
13
  @searchable = args[:searchable] == true
14
+ @description = args[:description]
13
15
  end
14
16
 
15
17
  def searchable
@@ -1,13 +1,26 @@
1
1
  module Avo
2
2
  module Filters
3
3
  class BaseFilter
4
+ PARAM_KEY = :filters unless const_defined?(:PARAM_KEY)
5
+
4
6
  class_attribute :name, default: "Filter"
5
7
  class_attribute :component, default: "boolean-filter"
6
- class_attribute :default, default: ""
8
+ class_attribute :default, default: nil
7
9
  class_attribute :template, default: "avo/base/select_filter"
10
+ class_attribute :empty_message, default: I18n.t("avo.no_options_available")
11
+
12
+ delegate :params, to: Avo::App
13
+
14
+ class << self
15
+ def decode_filters(filter_params)
16
+ JSON.parse(Base64.decode64(filter_params))
17
+ rescue
18
+ {}
19
+ end
20
+ end
8
21
 
9
22
  def apply_query(request, query, value)
10
- value.symbolize_keys! if value.is_a? Hash
23
+ value.stringify_keys! if value.is_a? Hash
11
24
 
12
25
  apply(request, query, value)
13
26
  end
@@ -15,6 +28,26 @@ module Avo
15
28
  def id
16
29
  self.class.name.underscore.tr("/", "_")
17
30
  end
31
+
32
+ # Get the applied value this filter.
33
+ # If it's not present return the default value.
34
+ def applied_or_default_value(applied_filters)
35
+ # Get the values for this particular filter
36
+ applied_value = applied_filters[self.class.to_s]
37
+
38
+ # Return that value if present
39
+ return applied_value unless applied_value.nil?
40
+
41
+ # Return that default
42
+ default
43
+ rescue
44
+ default
45
+ end
46
+
47
+ # Fetch the applied filters from the params
48
+ def applied_filters
49
+ self.class.decode_filters params[PARAM_KEY]
50
+ end
18
51
  end
19
52
  end
20
53
  end
@@ -2,6 +2,18 @@ module Avo
2
2
  module Filters
3
3
  class BooleanFilter < BaseFilter
4
4
  self.template = "avo/base/boolean_filter"
5
+
6
+ def selected_value(item, applied_filters)
7
+ # See if there are any applied rules for this particular filter
8
+ if applied_filters[self.class.to_s].present?
9
+ # Symbolize the keys because they are returned from de-serialization (JSON and Base64)
10
+ applied_filters[self.class.to_s].stringify_keys.dig(item.to_s)
11
+ else
12
+ applied_or_default_value(applied_filters).stringify_keys.dig(item.to_s)
13
+ end
14
+ rescue
15
+ false
16
+ end
5
17
  end
6
18
  end
7
19
  end
@@ -2,6 +2,21 @@ module Avo
2
2
  module Filters
3
3
  class MultipleSelectFilter < BaseFilter
4
4
  self.template = "avo/base/multiple_select_filter"
5
+
6
+ # The input expects an array of strings for the value
7
+ # Ex: ['admins', 'non_admins']
8
+ def selected_value(applied_filters)
9
+ # Get the values for this particular filter
10
+ applied_value = applied_filters[self.class.to_s]
11
+
12
+ # Return that value if present
13
+ return applied_value unless applied_value.nil?
14
+
15
+ # Return that default
16
+ return default unless default.nil?
17
+
18
+ []
19
+ end
5
20
  end
6
21
  end
7
22
  end
@@ -4,6 +4,10 @@ module Avo
4
4
  class_attribute :button_label
5
5
 
6
6
  self.template = "avo/base/text_filter"
7
+
8
+ def selected_value(applied_filters)
9
+ applied_or_default_value
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -0,0 +1,8 @@
1
+ module Avo
2
+ module Hosts
3
+ class AssociationScopeHost < BaseHost
4
+ option :parent
5
+ option :query
6
+ end
7
+ end
8
+ end