avo 2.18.1 → 2.20.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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -6
  3. data/Gemfile.lock +76 -77
  4. data/app/components/avo/actions_component.html.erb +1 -1
  5. data/app/components/avo/actions_component.rb +4 -4
  6. data/app/components/avo/base_component.rb +17 -0
  7. data/app/components/avo/card_component.html.erb +15 -15
  8. data/app/components/avo/card_component.rb +7 -5
  9. data/app/components/avo/field_wrapper_component.rb +7 -1
  10. data/app/components/avo/fields/has_one_field/show_component.rb +1 -2
  11. data/app/components/avo/fields/index_component.rb +4 -3
  12. data/app/components/avo/fields/select_field/edit_component.html.erb +1 -1
  13. data/app/components/avo/filters_component.html.erb +1 -1
  14. data/app/components/avo/filters_component.rb +2 -2
  15. data/app/components/avo/index/grid_item_component.rb +2 -2
  16. data/app/components/avo/index/resource_controls_component.rb +2 -2
  17. data/app/components/avo/index/table_row_component.html.erb +1 -1
  18. data/app/components/avo/item_switcher_component.html.erb +1 -1
  19. data/app/components/avo/panel_component.html.erb +1 -1
  20. data/app/components/avo/resource_component.rb +1 -1
  21. data/app/components/avo/resource_sidebar_component.rb +1 -6
  22. data/app/components/avo/sidebar/group_component.html.erb +18 -12
  23. data/app/components/avo/sidebar/heading_component.html.erb +17 -10
  24. data/app/components/avo/sidebar_profile_component.rb +3 -1
  25. data/app/components/avo/tab_group_component.rb +1 -1
  26. data/app/components/avo/views/resource_show_component.html.erb +1 -1
  27. data/app/controllers/avo/actions_controller.rb +27 -5
  28. data/app/controllers/avo/application_controller.rb +7 -0
  29. data/app/controllers/avo/base_controller.rb +30 -18
  30. data/app/controllers/avo/dashboards/cards_controller.rb +37 -0
  31. data/app/controllers/avo/dashboards_controller.rb +1 -0
  32. data/app/helpers/avo/application_helper.rb +5 -1
  33. data/app/javascript/js/controllers/search_controller.js +11 -2
  34. data/app/views/avo/actions/keep_modal_open.turbo_stream.erb +5 -0
  35. data/app/views/avo/actions/show.html.erb +2 -1
  36. data/app/views/avo/{cards → dashboards/cards}/_chartkick_card.html.erb +0 -0
  37. data/app/views/avo/{cards → dashboards/cards}/_metric_card.html.erb +0 -0
  38. data/app/views/avo/{cards → dashboards/cards}/chartkick_missing.html.erb +0 -0
  39. data/app/views/avo/{cards → dashboards/cards}/show.html.erb +0 -0
  40. data/app/views/avo/partials/_branding.html.erb +1 -0
  41. data/app/views/layouts/avo/application.html.erb +1 -1
  42. data/avo.gemspec +1 -2
  43. data/bin/init +11 -1
  44. data/bin/test +1 -0
  45. data/config/routes.rb +2 -2
  46. data/db/factories.rb +10 -0
  47. data/lib/avo/app.rb +14 -1
  48. data/lib/avo/base_action.rb +35 -10
  49. data/lib/avo/base_card.rb +3 -1
  50. data/lib/avo/base_resource.rb +21 -10
  51. data/lib/avo/concerns/breadcrumbs.rb +96 -0
  52. data/lib/avo/concerns/filters_session_handler.rb +43 -0
  53. data/lib/avo/concerns/has_fields.rb +2 -0
  54. data/lib/avo/concerns/has_html_attributes.rb +14 -16
  55. data/lib/avo/concerns/visible_items.rb +44 -0
  56. data/lib/avo/configuration/branding.rb +10 -2
  57. data/lib/avo/configuration.rb +3 -0
  58. data/lib/avo/dashboards/base_dashboard.rb +7 -1
  59. data/lib/avo/dynamic_router.rb +16 -15
  60. data/lib/avo/engine.rb +6 -9
  61. data/lib/avo/fields/base_field.rb +21 -12
  62. data/lib/avo/fields/concerns/has_default.rb +17 -0
  63. data/lib/avo/fields/concerns/is_readonly.rb +1 -1
  64. data/lib/avo/fields/concerns/is_required.rb +2 -2
  65. data/lib/avo/fields/has_base_field.rb +2 -0
  66. data/lib/avo/fields/key_value_field.rb +1 -1
  67. data/lib/avo/fields/select_field.rb +39 -34
  68. data/lib/avo/filters/base_filter.rb +24 -2
  69. data/lib/avo/hosts/resource_view_record_host.rb +7 -0
  70. data/lib/avo/hosts/visibility_host.rb +13 -0
  71. data/lib/avo/panel.rb +4 -1
  72. data/lib/avo/resources/controls/action.rb +1 -3
  73. data/lib/avo/sidebar.rb +1 -31
  74. data/lib/avo/tab.rb +5 -22
  75. data/lib/avo/version.rb +1 -1
  76. data/lib/avo.rb +9 -0
  77. data/lib/generators/avo/resource_generator.rb +281 -0
  78. data/lib/generators/avo/templates/action.tt +3 -0
  79. data/lib/generators/avo/templates/filters/boolean_filter.tt +3 -0
  80. data/lib/generators/avo/templates/filters/multiple_select_filter.tt +3 -0
  81. data/lib/generators/avo/templates/filters/select_filter.tt +3 -0
  82. data/lib/generators/avo/templates/filters/text_filter.tt +3 -0
  83. data/lib/generators/avo/templates/initializer/avo.tt +4 -0
  84. data/lib/generators/avo/templates/resource/controller.tt +1 -1
  85. data/lib/generators/avo/templates/resource/resource.tt +2 -2
  86. data/lib/generators/model_generator.rb +10 -0
  87. data/lib/generators/rails/avo_resource_generator.rb +11 -0
  88. data/public/avo-assets/avo.base.css +14 -14
  89. data/public/avo-assets/avo.base.js +1 -1
  90. data/public/avo-assets/avo.base.js.map +2 -2
  91. data/public/avo-assets/favicon.ico +0 -0
  92. data/{app/assets/svgs → public/avo-assets}/placeholder.svg +0 -0
  93. metadata +20 -25
  94. data/app/controllers/avo/cards_controller.rb +0 -35
  95. data/config/master.key +0 -1
@@ -41,7 +41,7 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
41
41
  }
42
42
  end
43
43
 
44
- helpers.resource_path(model: @resource.model, resource: @resource, **args)
44
+ helpers.resource_path(model: @resource.model, resource: parent_or_child_resource , **args)
45
45
  end
46
46
 
47
47
  def edit_path
@@ -55,7 +55,7 @@ class Avo::Index::ResourceControlsComponent < Avo::ResourceComponent
55
55
  }
56
56
  end
57
57
 
58
- helpers.edit_resource_path(model: @resource.model, resource: @resource, **args)
58
+ helpers.edit_resource_path(model: @resource.model, resource: parent_or_child_resource, **args)
59
59
  end
60
60
 
61
61
  def singular_resource_name
@@ -17,7 +17,7 @@
17
17
  </td>
18
18
  <% end %>
19
19
  <% @resource.get_fields(reflection: @reflection, only_root: true).each_with_index do |field, index| %>
20
- <%= render field.component_for_view(:index).new(field: field, resource: @resource, index: index, parent_model: @parent_model, parent_resource: @parent_resource) %>
20
+ <%= render field.component_for_view(:index).new(field: field, resource: @resource, reflection: @reflection, index: index, parent_model: @parent_model, parent_resource: @parent_resource) %>
21
21
  <% end %>
22
22
  <% if Avo.configuration.resource_controls_on_the_right? %>
23
23
  <td class="text-right whitespace-nowrap px-2" data-control="resource-controls">
@@ -6,7 +6,7 @@
6
6
  <%= render Avo::PanelComponent.new(name: item.name, description: item.description, index: index) do |c| %>
7
7
  <% c.body do %>
8
8
  <div class="divide-y">
9
- <% item.items.each_with_index do |field, index| %>
9
+ <% item.visible_items.each_with_index do |field, index| %>
10
10
  <%= render field
11
11
  .hydrate(resource: @resource, model: @resource.model, user: resource.user, view: view)
12
12
  .component_for_view(view)
@@ -4,7 +4,7 @@
4
4
  <div class="overflow-hidden flex flex-col">
5
5
  <% if display_breadcrumbs? %>
6
6
  <div class="breadcrumbs truncate mb-2">
7
- <%= helpers.render_breadcrumbs(separator: helpers.svg('chevron-right', class: 'inline-block h-3 stroke-current relative top-[-1px] ml-1' )) if Avo.configuration.display_breadcrumbs %>
7
+ <%= helpers.render_avo_breadcrumbs(separator: helpers.svg('chevron-right', class: 'inline-block h-3 stroke-current relative top-[-1px] ml-1' )) if Avo.configuration.display_breadcrumbs %>
8
8
  </div>
9
9
  <% end %>
10
10
  <div class="text-2xl tracking-normal font-semibold text-gray-800 truncate items-center flex flex-1" data-target="title">
@@ -92,7 +92,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
92
92
  end
93
93
 
94
94
  def sidebar_component(form: nil)
95
- Avo::ResourceSidebarComponent.new resource: @resource, fields: sidebar.items, params: params, view: view, form: form
95
+ Avo::ResourceSidebarComponent.new resource: @resource, fields: sidebar.visible_items, params: params, view: view, form: form
96
96
  end
97
97
 
98
98
  def has_reflection_and_is_read_only
@@ -5,6 +5,7 @@ class Avo::ResourceSidebarComponent < ViewComponent::Base
5
5
  attr_reader :params
6
6
  attr_reader :view
7
7
  attr_reader :form
8
+ attr_reader :fields
8
9
 
9
10
  def initialize(resource: nil, fields: nil, index: nil, params: nil, form: nil, view: nil)
10
11
  @resource = resource
@@ -14,12 +15,6 @@ class Avo::ResourceSidebarComponent < ViewComponent::Base
14
15
  @form = form
15
16
  end
16
17
 
17
- def fields
18
- @fields.filter do |field|
19
- field.visible_on? view
20
- end
21
- end
22
-
23
18
  def render?
24
19
  Avo::App.license.has_with_trial(:resource_sidebar)
25
20
  end
@@ -7,22 +7,28 @@
7
7
  <% end %>
8
8
  >
9
9
  <% if item.name.present? %>
10
- <div
11
- class="flex justify-between px-10 pr-2 pt-2 pb-0 text-gray-400"
12
- >
13
- <div class="flex items-center text-xs uppercase font-semibold leading-none">
14
- <%= item.name %>
15
- </div>
16
- <% if collapsable %>
17
- <div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
18
- data-action="click->menu#triggerCollapse"
10
+ <% if collapsable %>
11
+ <div
12
+ class="flex justify-between cursor-pointer px-10 pr-2 pt-2 pb-0 text-gray-400"
13
+ data-action="click->menu#triggerCollapse"
14
+ data-menu-key-param="<%= key %>"
15
+ >
16
+ <div class="flex items-center text-xs uppercase font-semibold leading-none">
17
+ <%= item.name %>
18
+ </div>
19
+ <div class="<%= 'rotate-90' if collapsed %>"
19
20
  data-menu-target="svg"
20
- data-menu-key-param="<%= key %>"
21
21
  >
22
22
  <%= helpers.svg 'heroicons/outline/chevron-down', class: "h-4 mr-0.5"%>
23
23
  </div>
24
- <% end %>
25
- </div>
24
+ </div>
25
+ <% else %>
26
+ <div class="flex justify-between px-10 pr-2 pt-2 pb-0 text-gray-400">
27
+ <div class="flex items-center text-xs uppercase font-semibold leading-none">
28
+ <%= item.name %>
29
+ </div>
30
+ </div>
31
+ <% end %>
26
32
  <% end %>
27
33
  <div class="w-full space-y-1 <%= 'hidden' if collapsed %>" data-menu-target="items">
28
34
  <% items.each do |item| %>
@@ -1,14 +1,21 @@
1
- <div class="flex justify-between px-4 pr-2 py-1 text-gray-500">
2
- <div class="flex items-center text-sm uppercase font-semibold leading-none space-x-1">
3
- <span class="min-w-[20px]"><%= icon %></span> <span><%= label %></span>
4
- </div>
5
- <% if collapsable %>
6
- <div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
7
- data-action="click->menu#triggerCollapse"
8
- data-menu-key-param="<%= key %>"
1
+ <% if collapsable %>
2
+ <div class="flex justify-between cursor-pointer px-4 pr-2 py-1 text-gray-500"
3
+ data-action="click->menu#triggerCollapse"
4
+ data-menu-key-param="<%= key %>"
5
+ >
6
+ <div class="flex items-center text-sm uppercase font-semibold leading-none space-x-1">
7
+ <span class="min-w-[20px]"><%= icon %></span> <span><%= label %></span>
8
+ </div>
9
+ <div class="<%= 'rotate-90' if collapsed %>"
9
10
  data-menu-target="svg"
10
11
  >
11
12
  <%= helpers.svg 'heroicons/outline/chevron-down', class: 'h-5'%>
12
13
  </div>
13
- <% end %>
14
- </div>
14
+ </div>
15
+ <% else %>
16
+ <div class="flex justify-between px-4 pr-2 py-1 text-gray-500">
17
+ <div class="flex items-center text-sm uppercase font-semibold leading-none space-x-1">
18
+ <span class="min-w-[20px]"><%= icon %></span> <span><%= label %></span>
19
+ </div>
20
+ </div>
21
+ <% end %>
@@ -34,7 +34,9 @@ class Avo::SidebarProfileComponent < ViewComponent::Base
34
34
  end
35
35
 
36
36
  def destroy_user_session_path
37
- "destroy_#{Avo.configuration.current_user_resource_name}_session_path".to_sym
37
+ # If `sign_out_path_name` is configured, use it. Otherwise construct the
38
+ # path name based on `current_user_resource_name`.
39
+ (Avo.configuration.sign_out_path_name || "destroy_#{Avo.configuration.current_user_resource_name}_session_path").to_sym
38
40
  end
39
41
 
40
42
  def can_destroy_user?
@@ -39,7 +39,7 @@ class Avo::TabGroupComponent < Avo::BaseComponent
39
39
 
40
40
  def visible_tabs
41
41
  tabs.select do |tab|
42
- !tab.empty?
42
+ tab.visible?
43
43
  end
44
44
  end
45
45
 
@@ -40,7 +40,7 @@
40
40
  <% end %>
41
41
  <% end %>
42
42
  <% elsif can_see_the_actions_button? && control.actions_list? %>
43
- <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view, exclude: control.exclude, style: control.style, color: control.color %>
43
+ <%= render Avo::ActionsComponent.new actions: @actions, resource: @resource, view: @view, exclude: control.exclude, style: control.style, color: control.color, label: control.label %>
44
44
  <% elsif control.edit_button? %>
45
45
  <% if @resource.authorization.authorize_action(:edit, raise_exception: false) %>
46
46
  <% end %>
@@ -51,7 +51,13 @@ module Avo
51
51
  end
52
52
 
53
53
  def set_action
54
- @action = action_class.new(model: @model, resource: @resource, user: _current_user, view: @view)
54
+ @action = action_class.new(
55
+ model: @model,
56
+ resource: @resource,
57
+ user: _current_user,
58
+ view: @view,
59
+ arguments: @resource.get_action_arguments(action_class)
60
+ )
55
61
  end
56
62
 
57
63
  def action_class
@@ -63,8 +69,10 @@ module Avo
63
69
  end
64
70
 
65
71
  def respond(response)
66
- response[:type] ||= :reload
67
72
  messages = get_messages response
73
+ return keep_modal_open(messages) if response[:keep_modal_open]
74
+
75
+ response[:type] ||= :reload
68
76
 
69
77
  if response[:type] == :download
70
78
  return send_data response[:path], filename: response[:filename]
@@ -73,9 +81,7 @@ module Avo
73
81
  respond_to do |format|
74
82
  format.html do
75
83
  # Flash the messages collected from the action
76
- messages.each do |message|
77
- flash[message[:type]] = message[:body]
78
- end
84
+ flash_messages messages
79
85
 
80
86
  if response[:type] == :redirect
81
87
  path = response[:path]
@@ -114,5 +120,21 @@ module Avo
114
120
  purpose: :select_all
115
121
  )
116
122
  end
123
+
124
+ def flash_messages(messages)
125
+ messages.each do |message|
126
+ flash[message[:type]] = message[:body]
127
+ end
128
+ end
129
+
130
+ def keep_modal_open(messages)
131
+ flash_messages messages
132
+
133
+ respond_to do |format|
134
+ format.turbo_stream do
135
+ render "keep_modal_open"
136
+ end
137
+ end
138
+ end
117
139
  end
118
140
  end
@@ -1,8 +1,15 @@
1
1
  module Avo
2
2
  class ApplicationController < ::ActionController::Base
3
+ if defined?(Pundit::Authorization)
4
+ Avo::ApplicationController.include Pundit::Authorization
5
+ elsif defined?(Pundit)
6
+ Avo::ApplicationController.include Pundit
7
+ end
8
+
3
9
  include Pagy::Backend
4
10
  include Avo::ApplicationHelper
5
11
  include Avo::UrlHelpers
12
+ include Avo::Concerns::Breadcrumbs
6
13
 
7
14
  protect_from_forgery with: :exception
8
15
  around_action :set_avo_locale
@@ -2,6 +2,8 @@ require_dependency "avo/application_controller"
2
2
 
3
3
  module Avo
4
4
  class BaseController < ApplicationController
5
+ include Avo::Concerns::FiltersSessionHandler
6
+
5
7
  before_action :set_resource_name
6
8
  before_action :set_resource
7
9
  before_action :hydrate_resource
@@ -57,7 +59,9 @@ module Avo
57
59
 
58
60
  # Apply filters to the current query
59
61
  filters_to_be_applied.each do |filter_class, filter_value|
60
- @query = filter_class.safe_constantize.new.apply_query request, @query, filter_value
62
+ @query = filter_class.safe_constantize.new(
63
+ arguments: @resource.get_filter_arguments(filter_class)
64
+ ).apply_query request, @query, filter_value
61
65
  end
62
66
 
63
67
  extra_pagy_params = {}
@@ -222,6 +226,9 @@ module Avo
222
226
  @errors = Array.wrap(exception.message)
223
227
  end
224
228
 
229
+ # Add the errors from the model
230
+ @errors = Array.wrap([@errors, @model.errors.full_messages]).compact
231
+
225
232
  succeeded
226
233
  end
227
234
 
@@ -302,26 +309,31 @@ module Avo
302
309
  end
303
310
 
304
311
  def set_filters
305
- @filters = @resource.get_filters.map do |filter_class|
306
- filter = filter_class.new
307
-
308
- filter
309
- end
312
+ @filters = @resource
313
+ .get_filters
314
+ .map do |filter|
315
+ filter[:class].new arguments: filter[:arguments]
316
+ end
317
+ .select do |filter|
318
+ filter.visible_in_view(resource: @resource, parent_model: @parent_model, parent_resource: @parent_resource)
319
+ end
310
320
  end
311
321
 
312
322
  def set_actions
313
323
  @actions = @resource
314
324
  .get_actions
315
325
  .map do |action|
316
- action.new(model: @model, resource: @resource, view: @view)
326
+ action[:class].new(model: @model, resource: @resource, view: @view, arguments: action[:arguments])
317
327
  end
318
328
  .select do |action|
319
- action.visible_in_view
329
+ action.visible_in_view(parent_model: @parent_model, parent_resource: @parent_resource)
320
330
  end
321
331
  end
322
332
 
323
333
  def set_applied_filters
324
- @applied_filters = Avo::Filters::BaseFilter.decode_filters(params[Avo::Filters::BaseFilter::PARAM_KEY])
334
+ reset_filters if params[:reset_filter]
335
+
336
+ @applied_filters = Avo::Filters::BaseFilter.decode_filters(fetch_filters)
325
337
 
326
338
  # Some filters react to others and will have to be merged into this
327
339
  @applied_filters = @applied_filters.merge reactive_filters
@@ -334,16 +346,16 @@ module Avo
334
346
 
335
347
  # Go through all filters
336
348
  @resource.get_filters
337
- .select do |filter_class|
338
- filter_class.instance_methods(false).include? :react
349
+ .select do |filter|
350
+ filter[:class].instance_methods(false).include? :react
339
351
  end
340
- .each do |filter_class|
352
+ .each do |filter|
341
353
  # Run the react method if it's present
342
- reaction = filter_class.new.react
354
+ reaction = filter[:class].new(arguments: filter[:arguments]).react
343
355
 
344
356
  next if reaction.nil?
345
357
 
346
- filter_reactions[filter_class.to_s] = filter_class.new.react
358
+ filter_reactions[filter[:class].to_s] = reaction
347
359
  end
348
360
 
349
361
  filter_reactions
@@ -353,11 +365,11 @@ module Avo
353
365
  def filters_to_be_applied
354
366
  filter_defaults = {}
355
367
 
356
- @resource.get_filters.each do |filter_class|
357
- filter = filter_class.new
368
+ @resource.get_filters.each do |filter|
369
+ filter = filter[:class].new arguments: filter[:arguments]
358
370
 
359
371
  unless filter.default.nil?
360
- filter_defaults[filter_class.to_s] = filter.default
372
+ filter_defaults[filter.class.to_s] = filter.default
361
373
  end
362
374
  end
363
375
 
@@ -476,7 +488,7 @@ module Avo
476
488
  end
477
489
 
478
490
  def destroy_fail_message
479
- @errors.present? ? @errors.first : t("avo.failed")
491
+ @errors.present? ? @errors.join(". ") : t("avo.failed")
480
492
  end
481
493
 
482
494
  def after_destroy_path
@@ -0,0 +1,37 @@
1
+ require_dependency "avo/application_controller"
2
+
3
+ module Avo
4
+ module Dashboards
5
+ class CardsController < ApplicationController
6
+ before_action :set_dashboard
7
+ before_action :set_card
8
+ before_action :detect_chartkick
9
+
10
+ def show
11
+ render(:chartkick_missing) unless @chartkick_installed
12
+ end
13
+
14
+ private
15
+
16
+ def set_dashboard
17
+ @dashboard = Avo::App.get_dashboard_by_id params[:dashboard_id]
18
+
19
+ raise ActionController::RoutingError.new("Not Found") if @dashboard.nil? || @dashboard.is_hidden?
20
+ end
21
+
22
+ def set_card
23
+ @card = @dashboard.item_at_index(params[:index].to_i).tap do |card|
24
+ card.hydrate(dashboard: @dashboard, params: params)
25
+ end
26
+ end
27
+
28
+ def detect_chartkick
29
+ @chartkick_installed = if @card.class.ancestors.map(&:to_s).include?("Avo::Dashboards::ChartkickCard")
30
+ defined?(Chartkick)
31
+ else
32
+ true
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -5,6 +5,7 @@ module Avo
5
5
  before_action :set_dashboard, only: :show
6
6
 
7
7
  def show
8
+ @page_title = @dashboard.name
8
9
  end
9
10
 
10
11
  private
@@ -102,7 +102,11 @@ module Avo
102
102
  end
103
103
 
104
104
  def root_path_without_url
105
- Avo::App.root_path.to_s.delete_prefix(request.base_url.to_s).delete_suffix "/"
105
+ Avo::App.root_path
106
+ .to_s
107
+ .delete_prefix(request.base_url.to_s)
108
+ .delete_suffix("/")
109
+ .gsub("/?", "?")
106
110
  rescue
107
111
  Avo.configuration.root_path
108
112
  end
@@ -76,7 +76,7 @@ export default class extends Controller {
76
76
  // This line fixes a bug where the search box would be duplicated on back navigation.
77
77
  this.autocompleteTarget.innerHTML = ''
78
78
 
79
- autocomplete({
79
+ const { destroy } = autocomplete({
80
80
  container: this.autocompleteTarget,
81
81
  placeholder: this.translationKeys.placeholder,
82
82
  translations: {
@@ -101,7 +101,7 @@ export default class extends Controller {
101
101
  })
102
102
 
103
103
  // document.addEventListener('turbo:before-render', destroy)
104
- // this.destroyMethod = destroy
104
+ this.destroyMethod = destroy
105
105
 
106
106
  // When using search for belongs-to
107
107
  if (this.buttonTarget.dataset.shouldBeDisabled !== 'true') {
@@ -109,6 +109,15 @@ export default class extends Controller {
109
109
  }
110
110
  }
111
111
 
112
+ disconnect() {
113
+ // Don't leave open autocompletes around when disconnected. Otherwise it will still
114
+ // be visible when navigating back to this page.
115
+ if (this.destroyMethod) {
116
+ this.destroyMethod()
117
+ this.destroyMethod = null
118
+ }
119
+ }
120
+
112
121
  addSource(resourceName, data) {
113
122
  const that = this
114
123
 
@@ -0,0 +1,5 @@
1
+ <turbo-stream action="append" target="alerts">
2
+ <template>
3
+ <%= render Avo::FlashAlertsComponent.new flashes: flash %>
4
+ </template>
5
+ </turbo-stream>
@@ -34,7 +34,8 @@
34
34
  </div>
35
35
  <% end %>
36
36
  <% c.controls do %>
37
- <%= a_button data: { action: 'click->modal#close' },
37
+ <%= a_button type: :button,
38
+ data: { action: 'click->modal#close' },
38
39
  size: :sm,
39
40
  color: :gray do %>
40
41
  <%= @action.cancel_button_label %>
@@ -3,3 +3,4 @@
3
3
  <%= Avo.configuration.branding.css_colors %>
4
4
  }
5
5
  </style>
6
+ <%= favicon_link_tag Avo.configuration.branding.favicon %>
@@ -46,7 +46,7 @@
46
46
  <%= turbo_frame_tag 'actions_show' %>
47
47
  <%= turbo_frame_tag 'attach_modal' %>
48
48
  <%= turbo_frame_tag 'destroy_attachment_form' %>
49
- <%= turbo_frame_tag 'alerts', class: "fixed inset-0 bottom-0 flex flex-col space-y-4 items-end justify-right px-4 py-6 sm:p-6 justify-end z-50 pointer-events-none" do %>
49
+ <%= turbo_frame_tag 'alerts', class: "fixed inset-0 bottom-0 flex flex-col space-y-4 items-end justify-right px-4 py-6 sm:p-6 justify-end z-[100] pointer-events-none" do %>
50
50
  <%= render Avo::FlashAlertsComponent.new flashes: flash %>
51
51
  <% # In case we have other general error messages %>
52
52
  <% if @errors.present? %>
data/avo.gemspec CHANGED
@@ -35,14 +35,13 @@ Gem::Specification.new do |spec|
35
35
  spec.add_dependency "activerecord", ">= 6.0"
36
36
  spec.add_dependency "actionview", ">= 6.0"
37
37
  spec.add_dependency "pagy"
38
- spec.add_dependency "zeitwerk"
38
+ spec.add_dependency "zeitwerk", ">= 2.6.2"
39
39
  spec.add_dependency "httparty"
40
40
  spec.add_dependency "active_link_to"
41
41
  spec.add_dependency "view_component"
42
42
  spec.add_dependency "turbo-rails"
43
43
  spec.add_dependency "addressable"
44
44
  spec.add_dependency "meta-tags"
45
- spec.add_dependency "breadcrumbs_on_rails"
46
45
  spec.add_dependency "dry-initializer"
47
46
  spec.add_dependency "docile"
48
47
  spec.add_dependency "inline_svg"
data/bin/init CHANGED
@@ -9,7 +9,17 @@ app_root do
9
9
  )
10
10
 
11
11
  header 'Configuring git'
12
- run! 'git remote add upstream https://github.com/avo-hq/avo.git'
12
+ target_upstream = "https://github.com/avo-hq/avo.git"
13
+ current_upstream = `git config --get remote.upstream.url`.chomp
14
+ if current_upstream.nil? || current_upstream.empty?
15
+ puts "Adding new remote 'upstream' to #{target_upstream}"
16
+ run! "git remote add upstream #{target_upstream}"
17
+ elsif current_upstream != target_upstream
18
+ puts "Updating existing remote 'upstream' to #{target_upstream}"
19
+ run! "git remote set-url upstream #{target_upstream}"
20
+ else
21
+ puts "Remote 'upstream' already points to #{target_upstream}, no change"
22
+ end
13
23
 
14
24
  header 'Installing gems'
15
25
  run! 'bundle install'
data/bin/test CHANGED
@@ -7,6 +7,7 @@ export NODE_ENV="test"
7
7
  if [[ -z "$1" ]] || [[ "$1" == "unit" ]]; then
8
8
  bundle exec rspec ./spec --tag type:feature
9
9
  bundle exec rspec ./spec --tag type:controller
10
+ bundle exec rspec ./spec --tag type:component
10
11
  fi;
11
12
 
12
13
  # Run system tests
data/config/routes.rb CHANGED
@@ -7,7 +7,7 @@ Avo::Engine.routes.draw do
7
7
  post "/rails/active_storage/direct_uploads", to: "/active_storage/direct_uploads#create"
8
8
 
9
9
  resources :dashboards do
10
- resources :cards
10
+ resources :cards, controller: "dashboards/cards"
11
11
  end
12
12
 
13
13
  scope "avo_api", as: "avo_api" do
@@ -33,7 +33,7 @@ Avo::Engine.routes.draw do
33
33
 
34
34
  # Generate resource routes as below:
35
35
  # resources :posts
36
- Avo::DynamicRouter.routes(self)
36
+ Avo::DynamicRouter.routes
37
37
 
38
38
  # Associations
39
39
  get "/:resource_name/:id/:related_name/new", to: "associations#new", as: "associations_new"
data/db/factories.rb CHANGED
@@ -60,6 +60,16 @@ FactoryBot.define do
60
60
  type { "Spouse" }
61
61
  end
62
62
 
63
+ factory :sibling do
64
+ name { "#{Faker::Name.first_name} #{Faker::Name.last_name}" }
65
+ type { "Sibling" }
66
+ end
67
+
68
+ factory :brother do
69
+ name { "#{Faker::Name.first_name} #{Faker::Name.last_name}" }
70
+ type { "Brother" }
71
+ end
72
+
63
73
  factory :fish do
64
74
  name { %w[Tilapia Salmon Trout Catfish Pangasius Carp].sample }
65
75
  end
data/lib/avo/app.rb CHANGED
@@ -17,6 +17,17 @@ module Avo
17
17
  class_attribute :error_messages
18
18
 
19
19
  class << self
20
+ def eager_load(entity)
21
+ paths = Avo::ENTITIES.fetch entity
22
+
23
+ return unless paths.present?
24
+
25
+ pathname = Rails.root.join(*paths)
26
+ if pathname.directory?
27
+ Rails.autoloaders.main.eager_load_dir(pathname.to_s)
28
+ end
29
+ end
30
+
20
31
  def boot
21
32
  init_fields
22
33
 
@@ -91,7 +102,7 @@ module Avo
91
102
  has_model = resource.model_class.present?
92
103
 
93
104
  unless has_model
94
- possible_model = resource.class.to_s.gsub 'Resource', ''
105
+ possible_model = resource.class.to_s.gsub "Resource", ""
95
106
 
96
107
  Avo::App.error_messages.push({
97
108
  url: "https://docs.avohq.io/2.0/resources.html#custom-model-class",
@@ -119,6 +130,8 @@ module Avo
119
130
  end
120
131
 
121
132
  def init_dashboards
133
+ eager_load :dashboards unless Rails.application.config.eager_load
134
+
122
135
  self.dashboards = Dashboards::BaseDashboard.descendants
123
136
  .select do |dashboard|
124
137
  dashboard != Dashboards::BaseDashboard