avo 2.19.0 → 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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2b092c8d9df1951b5ccd6a07b31c8c96a6cb68846ab2b2342f43082449a5ced
4
- data.tar.gz: 5bb4985123f45ad9aeca49892559d43a80130a29ff823fc6c7f35127e5de3e5c
3
+ metadata.gz: 032f3c926677645e9939a30205693425f4c44c4db874d7e30849c600e13be8a1
4
+ data.tar.gz: 110d46843bf447a9e1a1215e3d4e17e4c8bf64ca4f88889d72a504c6b9cef06e
5
5
  SHA512:
6
- metadata.gz: e28f40d6e4559541cd2e08a76753ef87bcbfe54a48bd3ba2c7ea368436762bb287ddbac6641df9b18f6196071dd6bd7bb3f5e2f6e0bb95e2b7b10a5ab0cc6a2b
7
- data.tar.gz: c6a64520f187b1e725b167bc396e23d6177c9c83e8f5ff2864a56cd12aed87ec757bfd34ee76955e93718268e888c9693dfa6899d6633e37f93874d2d86cbd73
6
+ metadata.gz: feed3402af12295a712d16f45ced2248c6fcb0663cdad1d4e3b160f9d706e26322ce2966f64f1f6d0fe622f13f2019be8b12d8c16afa7f009ddefe175fd0a7f5
7
+ data.tar.gz: 28c50de8194d7c649b2c629c988511f1470b00dbaa5d7a60013f743959fecff0b7e97e5c5383548552e412bdea05b9224910c38d681ddbefea25116ade529145
data/Gemfile CHANGED
@@ -121,8 +121,7 @@ gem "appraisal"
121
121
 
122
122
  gem 'meta-tags'
123
123
 
124
- gem 'breadcrumbs_on_rails'
125
-
124
+ gem 'manifester'
126
125
 
127
126
  # Search
128
127
  gem 'ransack'
data/Gemfile.lock CHANGED
@@ -1,12 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (2.19.0)
4
+ avo (2.20.0)
5
5
  actionview (>= 6.0)
6
6
  active_link_to
7
7
  activerecord (>= 6.0)
8
8
  addressable
9
- breadcrumbs_on_rails
10
9
  docile
11
10
  dry-initializer
12
11
  httparty
@@ -127,8 +126,6 @@ GEM
127
126
  bootsnap (1.13.0)
128
127
  msgpack (~> 1.2)
129
128
  brakeman (5.3.1)
130
- breadcrumbs_on_rails (4.1.0)
131
- railties (>= 5.0)
132
129
  builder (3.2.4)
133
130
  bump (0.10.0)
134
131
  bundler-integrity (1.0.9)
@@ -238,6 +235,9 @@ GEM
238
235
  nokogiri (>= 1.5.9)
239
236
  mail (2.7.1)
240
237
  mini_mime (>= 0.1.1)
238
+ manifester (0.1.8)
239
+ rails (>= 6.0)
240
+ zeitwerk
241
241
  marcel (1.0.2)
242
242
  matrix (0.4.2)
243
243
  meta-tags (2.17.0)
@@ -409,7 +409,7 @@ GEM
409
409
  tzinfo (2.0.5)
410
410
  concurrent-ruby (~> 1.0)
411
411
  unicode-display_width (2.2.0)
412
- view_component (2.74.1)
412
+ view_component (2.77.0)
413
413
  activesupport (>= 5.0.0, < 8.0)
414
414
  concurrent-ruby (~> 1.0)
415
415
  method_source (~> 1.0)
@@ -454,7 +454,6 @@ DEPENDENCIES
454
454
  aws-sdk-s3
455
455
  bootsnap (>= 1.4.2)
456
456
  brakeman
457
- breadcrumbs_on_rails
458
457
  bump
459
458
  bundler-integrity (~> 1.0)
460
459
  byebug
@@ -482,6 +481,7 @@ DEPENDENCIES
482
481
  jsbundling-rails
483
482
  launchy
484
483
  listen (>= 3.5.1)
484
+ manifester
485
485
  meta-tags
486
486
  net-smtp
487
487
  pg (>= 0.18, < 2.0)
@@ -1,18 +1,18 @@
1
1
  <div class="relative flex-1 flex flex-col justify-between h-full"
2
2
  data-controller="dashboard-card"
3
3
  data-dashboard-card-target="card"
4
- data-refresh-every="<%= @card.refresh_every %>"
5
- data-card-id="<%= @card.id %>"
6
- data-card-index="<%= @card.index %>">
7
- <% if @card.class.display_header %>
4
+ data-refresh-every="<%= card.refresh_every %>"
5
+ data-card-id="<%= card.id %>"
6
+ data-card-index="<%= card.index %>">
7
+ <% if card.class.display_header %>
8
8
  <div class="px-4 pt-4">
9
9
  <div class="flex justify-between items-center min-h-6">
10
10
  <div class="text-base font-bold text-gray-700 leading-none">
11
- <%= @card.label %>
11
+ <%= card.label %>
12
12
  </div>
13
13
  <div data-controller="select">
14
- <% if @card.type.in?([:metric, :chartkick, :partial]) && @card.ranges.present? %>
15
- <%= select_tag "#{@card.id}_#{@card.index}_range", options_for_select(@card.ranges, @card.range),
14
+ <% if card.type.in?([:metric, :chartkick, :partial]) && card.ranges.present? %>
15
+ <%= select_tag "#{card.id}_#{card.index}_range", options_for_select(card.ranges, card.range),
16
16
  class: 'appearance-none inline-flex bg-blue-gray-100 disabled:bg-blue-gray-300 disabled:cursor-not-allowed focus:bg-white text-sm text-blue-gray-700 disabled:text-blue-gray-700 leading-none rounded-md py-px px-2 leading-tight border outline-none outline w-24',
17
17
  data: {
18
18
  target: 'select',
@@ -24,18 +24,18 @@
24
24
  </div>
25
25
  </div>
26
26
  <% end %>
27
- <% if @card.type == :chartkick %>
28
- <%= render 'chartkick_card', card: @card %>
29
- <% elsif @card.type === :partial %>
30
- <%= render @card.class.partial %>
31
- <% elsif @card.type === :metric %>
27
+ <% if card.type == :chartkick %>
28
+ <%= render 'chartkick_card', card: card %>
29
+ <% elsif card.type === :partial %>
30
+ <%= render card.class.partial %>
31
+ <% elsif card.type === :metric %>
32
32
  <div class="flex-1 flex px-4 pb-4">
33
- <%= render 'metric_card', card: @card %>
33
+ <%= render 'metric_card', card: card %>
34
34
  </div>
35
35
  <% end %>
36
- <% if @card.description.present? %>
36
+ <% if card.description.present? %>
37
37
  <%= content_tag :div, class: "absolute inset-auto bottom-0 right-0 mb-4 mr-4",
38
- title: @card.description,
38
+ title: card.description,
39
39
  data: {
40
40
  target: 'card-description',
41
41
  tippy: 'tooltip',
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::CardComponent < ViewComponent::Base
4
+ attr_reader :card
5
+
4
6
  def initialize(card: nil)
5
7
  @card = card
6
8
 
@@ -8,16 +10,16 @@ class Avo::CardComponent < ViewComponent::Base
8
10
  end
9
11
 
10
12
  def render?
11
- !@card.nil?
13
+ card.present?
12
14
  end
13
15
 
14
16
  # Initializing the card byt running the query method.
15
17
  # We'll still keep the query block around for compatibility reasons.
16
18
  def init_card
17
- if @card.respond_to? :query
18
- @card.query
19
- elsif @card.query_block.present?
20
- @card.compute_result
19
+ if card.respond_to? :query
20
+ card.query
21
+ elsif card.query_block.present?
22
+ card.compute_result
21
23
  end
22
24
  end
23
25
  end
@@ -1,5 +1,5 @@
1
1
  <%= field_wrapper **field_wrapper_args do %>
2
- <%= @form.select @field.id, @field.options_for_select, {
2
+ <%= @form.select @field.id, options_for_select(@field.options_for_select, selected: @field.value), {
3
3
  include_blank: @field.include_blank
4
4
  },
5
5
  aria: {
@@ -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">
@@ -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 %>
@@ -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
@@ -9,6 +9,7 @@ module Avo
9
9
  include Pagy::Backend
10
10
  include Avo::ApplicationHelper
11
11
  include Avo::UrlHelpers
12
+ include Avo::Concerns::Breadcrumbs
12
13
 
13
14
  protect_from_forgery with: :exception
14
15
  around_action :set_avo_locale
@@ -59,7 +59,9 @@ module Avo
59
59
 
60
60
  # Apply filters to the current query
61
61
  filters_to_be_applied.each do |filter_class, filter_value|
62
- @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
63
65
  end
64
66
 
65
67
  extra_pagy_params = {}
@@ -224,6 +226,9 @@ module Avo
224
226
  @errors = Array.wrap(exception.message)
225
227
  end
226
228
 
229
+ # Add the errors from the model
230
+ @errors = Array.wrap([@errors, @model.errors.full_messages]).compact
231
+
227
232
  succeeded
228
233
  end
229
234
 
@@ -306,8 +311,8 @@ module Avo
306
311
  def set_filters
307
312
  @filters = @resource
308
313
  .get_filters
309
- .map do |filter_class|
310
- filter_class.new
314
+ .map do |filter|
315
+ filter[:class].new arguments: filter[:arguments]
311
316
  end
312
317
  .select do |filter|
313
318
  filter.visible_in_view(resource: @resource, parent_model: @parent_model, parent_resource: @parent_resource)
@@ -318,7 +323,7 @@ module Avo
318
323
  @actions = @resource
319
324
  .get_actions
320
325
  .map do |action|
321
- action.new(model: @model, resource: @resource, view: @view)
326
+ action[:class].new(model: @model, resource: @resource, view: @view, arguments: action[:arguments])
322
327
  end
323
328
  .select do |action|
324
329
  action.visible_in_view(parent_model: @parent_model, parent_resource: @parent_resource)
@@ -341,16 +346,16 @@ module Avo
341
346
 
342
347
  # Go through all filters
343
348
  @resource.get_filters
344
- .select do |filter_class|
345
- filter_class.instance_methods(false).include? :react
349
+ .select do |filter|
350
+ filter[:class].instance_methods(false).include? :react
346
351
  end
347
- .each do |filter_class|
352
+ .each do |filter|
348
353
  # Run the react method if it's present
349
- reaction = filter_class.new.react
354
+ reaction = filter[:class].new(arguments: filter[:arguments]).react
350
355
 
351
356
  next if reaction.nil?
352
357
 
353
- filter_reactions[filter_class.to_s] = filter_class.new.react
358
+ filter_reactions[filter[:class].to_s] = reaction
354
359
  end
355
360
 
356
361
  filter_reactions
@@ -360,11 +365,11 @@ module Avo
360
365
  def filters_to_be_applied
361
366
  filter_defaults = {}
362
367
 
363
- @resource.get_filters.each do |filter_class|
364
- filter = filter_class.new
368
+ @resource.get_filters.each do |filter|
369
+ filter = filter[:class].new arguments: filter[:arguments]
365
370
 
366
371
  unless filter.default.nil?
367
- filter_defaults[filter_class.to_s] = filter.default
372
+ filter_defaults[filter.class.to_s] = filter.default
368
373
  end
369
374
  end
370
375
 
@@ -483,7 +488,7 @@ module Avo
483
488
  end
484
489
 
485
490
  def destroy_fail_message
486
- @errors.present? ? @errors.first : t("avo.failed")
491
+ @errors.present? ? @errors.join(". ") : t("avo.failed")
487
492
  end
488
493
 
489
494
  def after_destroy_path
@@ -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
@@ -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 %>
@@ -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
@@ -42,7 +42,6 @@ Gem::Specification.new do |spec|
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/lib/avo/app.rb CHANGED
@@ -22,7 +22,10 @@ module Avo
22
22
 
23
23
  return unless paths.present?
24
24
 
25
- Rails.autoloaders.main.eager_load_dir(Rails.root.join(*paths).to_s)
25
+ pathname = Rails.root.join(*paths)
26
+ if pathname.directory?
27
+ Rails.autoloaders.main.eager_load_dir(pathname.to_s)
28
+ end
26
29
  end
27
30
 
28
31
  def boot
@@ -19,6 +19,7 @@ module Avo
19
19
  attr_accessor :model
20
20
  attr_accessor :resource
21
21
  attr_accessor :user
22
+ attr_reader :arguments
22
23
 
23
24
  delegate :view, to: :class
24
25
  delegate :context, to: ::Avo::App
@@ -56,11 +57,12 @@ module Avo
56
57
  self.class.to_s.demodulize.underscore.humanize(keep_id_suffix: true)
57
58
  end
58
59
 
59
- def initialize(model: nil, resource: nil, user: nil, view: nil)
60
+ def initialize(model: nil, resource: nil, user: nil, view: nil, arguments: {})
60
61
  self.class.model = model if model.present?
61
62
  self.class.resource = resource if resource.present?
62
63
  self.class.user = user if user.present?
63
64
  self.class.view = view if view.present?
65
+ @arguments = arguments
64
66
 
65
67
  self.class.message ||= I18n.t("avo.are_you_sure_you_want_to_run_this_option")
66
68
  self.class.confirm_button_label ||= I18n.t("avo.run")
@@ -132,6 +134,7 @@ module Avo
132
134
  parent_resource: parent_resource,
133
135
  resource: self.class.resource,
134
136
  view: self.class.view,
137
+ arguments: arguments
135
138
  ).handle
136
139
  end
137
140
 
@@ -146,6 +149,12 @@ module Avo
146
149
  end
147
150
 
148
151
  def fail(text)
152
+ Rails.logger.warn "DEPRECATION WARNING: Action fail method is deprecated in favor of error method and will be removed from Avo version 3.0.0"
153
+
154
+ error text
155
+ end
156
+
157
+ def error(text)
149
158
  add_message text, :error
150
159
 
151
160
  self
@@ -163,6 +172,12 @@ module Avo
163
172
  self
164
173
  end
165
174
 
175
+ def keep_modal_open
176
+ response[:keep_modal_open] = true
177
+
178
+ self
179
+ end
180
+
166
181
  # Add a placeholder silent message from when a user wants to do a redirect action or something similar
167
182
  def silent
168
183
  add_message nil, :silent
data/lib/avo/base_card.rb CHANGED
@@ -15,6 +15,7 @@ module Avo
15
15
 
16
16
  attr_accessor :dashboard
17
17
  attr_accessor :options
18
+ attr_accessor :arguments
18
19
  attr_accessor :index
19
20
  attr_accessor :params
20
21
 
@@ -26,9 +27,10 @@ module Avo
26
27
  end
27
28
  end
28
29
 
29
- def initialize(dashboard:, options: {}, index: 0, cols: nil, rows: nil, label: nil, description: nil, refresh_every: nil)
30
+ def initialize(dashboard:, options: {}, arguments: {}, index: 0, cols: nil, rows: nil, label: nil, description: nil, refresh_every: nil)
30
31
  @dashboard = dashboard
31
32
  @options = options
33
+ @arguments = arguments
32
34
  @index = index
33
35
  @cols = cols
34
36
  @rows = rows
@@ -62,16 +62,18 @@ module Avo
62
62
  self.grid_loader = grid_collector
63
63
  end
64
64
 
65
- def action(action_class)
65
+ def action(action_class, arguments: {})
66
66
  self.actions_loader ||= Avo::Loaders::Loader.new
67
67
 
68
- self.actions_loader.use action_class
68
+ action = { class: action_class, arguments: arguments }
69
+ self.actions_loader.use action
69
70
  end
70
71
 
71
- def filter(filter_class)
72
+ def filter(filter_class, arguments: {})
72
73
  self.filters_loader ||= Avo::Loaders::Loader.new
73
74
 
74
- self.filters_loader.use filter_class
75
+ filter = { class: filter_class , arguments: arguments }
76
+ self.filters_loader.use filter
75
77
  end
76
78
 
77
79
  # This is the search_query scope
@@ -170,12 +172,24 @@ module Avo
170
172
  self.class.filters_loader.bag
171
173
  end
172
174
 
175
+ def get_filter_arguments(filter_class)
176
+ filter = get_filters.find { |filter| filter[:class] == filter_class.constantize }
177
+
178
+ filter[:arguments]
179
+ end
180
+
173
181
  def get_actions
174
182
  return [] if self.class.actions_loader.blank?
175
183
 
176
184
  self.class.actions_loader.bag
177
185
  end
178
186
 
187
+ def get_action_arguments(action_class)
188
+ action = get_actions.find { |action| action[:class].to_s == action_class.to_s }
189
+
190
+ action[:arguments]
191
+ end
192
+
179
193
  def default_panel_name
180
194
  return @params[:related_name].capitalize if @params.present? && @params[:related_name].present?
181
195
 
@@ -0,0 +1,96 @@
1
+ module Avo
2
+ module Concerns
3
+ # This is a custom implementation of breadcrumbs largely based on breadcrumbs_on_rails gem
4
+ # created by Simone Carletti (@weppos) and released on MIT license.
5
+ #
6
+ # https://github.com/weppos/breadcrumbs_on_rails
7
+ #
8
+ # The reason to use custom implementation is to
9
+ # * Avoid naming conflicts with other gems adding helpers like `breadcrumbs`
10
+ # * Reduce number of dependencies
11
+ module Breadcrumbs
12
+ extend ActiveSupport::Concern
13
+
14
+ included do |base|
15
+ helper HelperMethods
16
+ extend ClassMethods
17
+ helper_method :add_breadcrumb, :avo_breadcrumbs
18
+ end
19
+
20
+ Crumb = Struct.new(:name, :path) unless defined?(Crumb)
21
+
22
+ class Builder
23
+ DEFAULT_SEPARATOR = " &raquo; ".freeze unless defined?(DEFAULT_SEPARATOR)
24
+
25
+ attr_reader :context, :options
26
+
27
+ def initialize(context, options)
28
+ @context = context
29
+ @options = options
30
+ end
31
+
32
+ def render
33
+ separator = options.fetch(:separator, DEFAULT_SEPARATOR)
34
+ breadcrumbs.map(&method(:render_element)).join(separator)
35
+ end
36
+
37
+ private
38
+
39
+ def breadcrumbs
40
+ context.avo_breadcrumbs
41
+ end
42
+
43
+ def render_element(element)
44
+ content = element.path.nil? ? compute_name(element) : context.link_to_unless_current(compute_name(element), compute_path(element))
45
+ options[:tag] ? context.content_tag(options[:tag], content) : ERB::Util.h(content)
46
+ end
47
+
48
+ def compute_name(element)
49
+ case name = element.name
50
+ when Symbol
51
+ context.send(name)
52
+ when Proc
53
+ name.call(context)
54
+ else
55
+ name.to_s
56
+ end
57
+ end
58
+
59
+ def compute_path(element)
60
+ case path = element.path
61
+ when Symbol
62
+ context.send(path)
63
+ when Proc
64
+ path.call(context)
65
+ else
66
+ context.url_for(path)
67
+ end
68
+ end
69
+ end
70
+
71
+ module ClassMethods
72
+ def add_breadcrumb(name, path = nil)
73
+ before_action(filter_options) do |controller|
74
+ controller.send(:add_breadcrumb, name, path)
75
+ end
76
+ end
77
+ end
78
+
79
+ def add_breadcrumb(name, path = nil)
80
+ avo_breadcrumbs << Crumb.new(name, path)
81
+ end
82
+
83
+ def avo_breadcrumbs
84
+ @avo_breadcrumbs ||= []
85
+ end
86
+
87
+ module HelperMethods
88
+ def render_avo_breadcrumbs(options = {}, &block)
89
+ builder = Builder.new(self, options)
90
+ content = builder.render.html_safe
91
+ block_given? ? capture(content, &block) : content
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -16,12 +16,14 @@ module Avo
16
16
  default_attribute_value name
17
17
  end
18
18
 
19
- attributes = if html_builder.is_a? Hash
20
- get_html_from_hash name, element: element, view: view
21
- elsif html_builder.is_a? Avo::HTML::Builder
22
- get_html_from_block name, element: element, view: view
23
- elsif html_builder.nil?
24
- # Handle empty html_builder by returning an empty state
19
+ parsed = parse_html
20
+
21
+ attributes = if parsed.is_a? Hash
22
+ get_html_from_hash name, element: element, hash: parsed, view: view
23
+ elsif parsed.is_a? Avo::HTML::Builder
24
+ get_html_from_block name, element: element, html_builder: parsed, view: view
25
+ elsif parsed.nil?
26
+ # Handle empty parsed by returning an empty state
25
27
  default_attribute_value name
26
28
  end
27
29
 
@@ -30,19 +32,15 @@ module Avo
30
32
 
31
33
  private
32
34
 
33
- def html_builder
34
- return @parsed_html if @parsed_html.present?
35
-
35
+ # Returns Hash, HTML::Builder, or nil.
36
+ def parse_html
36
37
  return if @html.nil?
37
38
 
38
- # Memoize the value
39
- @parsed_html = if @html.is_a? Hash
39
+ if @html.is_a? Hash
40
40
  @html
41
41
  elsif @html.respond_to? :call
42
42
  Avo::HTML::Builder.parse_block(record: model, resource: resource, &@html)
43
43
  end
44
-
45
- @parsed_html
46
44
  end
47
45
 
48
46
  def default_attribute_value(name)
@@ -64,7 +62,7 @@ module Avo
64
62
  end
65
63
  end
66
64
 
67
- def get_html_from_block(name = nil, element:, view:)
65
+ def get_html_from_block(name = nil, element:, html_builder:, view:)
68
66
  values = []
69
67
 
70
68
  # get view ancestor
@@ -83,9 +81,9 @@ module Avo
83
81
  merge_values_as(as: values_type, values: values)
84
82
  end
85
83
 
86
- def get_html_from_hash(name = nil, element:, view:)
84
+ def get_html_from_hash(name = nil, element:, hash:, view:)
87
85
  # @todo: what if this is not a Hash but a string?
88
- html_builder.dig(view, element, name) || {}
86
+ hash.dig(view, element, name) || {}
89
87
  end
90
88
 
91
89
  # Merge the values from all possible locations.
@@ -12,7 +12,12 @@ module Avo
12
12
  class_attribute :index, default: 0
13
13
 
14
14
  class << self
15
- def card(klass, label: nil, description: nil, cols: nil, rows: nil, refresh_every: nil, options: {})
15
+ def options_deprecation_message
16
+ Rails.logger.warn "DEPRECATION WARNING: Card options parameter is deprecated in favor of arguments parameter and will be removed from Avo version 3.0.0"
17
+ end
18
+
19
+ def card(klass, label: nil, description: nil, cols: nil, rows: nil, refresh_every: nil, options: {}, arguments: {})
20
+ options_deprecation_message if options.present?
16
21
  self.items_holder ||= []
17
22
 
18
23
  self.items_holder << klass.new(dashboard: self,
@@ -22,6 +27,7 @@ module Avo
22
27
  rows: rows,
23
28
  refresh_every: refresh_every,
24
29
  options: options,
30
+ arguments: arguments,
25
31
  index: index
26
32
  )
27
33
  self.index += 1
@@ -3,7 +3,7 @@ module Avo
3
3
  class SelectField < BaseField
4
4
  include Avo::Fields::FieldExtensions::HasIncludeBlank
5
5
 
6
- attr_reader :options
6
+ attr_reader :options_from_args
7
7
  attr_reader :enum
8
8
  attr_reader :display_value
9
9
 
@@ -12,51 +12,56 @@ module Avo
12
12
 
13
13
  super(id, **args, &block)
14
14
 
15
- @options = args[:options] || args[:enum]
16
- @options = ActiveSupport::HashWithIndifferentAccess.new @options if @options.is_a? Hash
15
+ @options_from_args = if args[:options].is_a? Hash
16
+ ActiveSupport::HashWithIndifferentAccess.new args[:options]
17
+ else
18
+ args[:options]
19
+ end
17
20
  @enum = args[:enum].present? ? args[:enum] : nil
18
21
  @display_value = args[:display_value].present? ? args[:display_value] : false
19
22
  end
20
23
 
21
24
  def options_for_select
22
- if options.respond_to? :call
23
- computed_options = options.call model: model, resource: resource, view: view, field: self
24
- if display_value
25
- computed_options.map { |label, value| [value, value] }.to_h
26
- else
27
- computed_options
28
- end
29
- elsif enum.present?
30
- if display_value
31
- options.invert
32
- else
33
- # We need to use the label attribute as the option value because Rails casts it like that
34
- options.map { |label, value| [label, label] }.to_h
35
- end
36
- elsif display_value
37
- options.map { |label, value| [value, value] }.to_h
38
- else
39
- options
25
+ # If options are array don't need any pre-process
26
+ return options if options.is_a?(Array)
27
+
28
+ # If options are enum we invert the enum if display value, else (see next comment)
29
+ if enum.present?
30
+ return enum.invert if display_value
31
+
32
+ # We need to use the label attribute as the option value because Rails casts it like that
33
+ return enum.map { |label, value| [label, label] }.to_h
40
34
  end
35
+
36
+ # When code arrive here it means options are Hash
37
+ # If display_value is true we only need to return the values of the Hash
38
+ display_value ? options.values : options
41
39
  end
42
40
 
43
41
  def label
44
- if options.respond_to? :call
45
- computed_options = options.call model: model, resource: resource, view: view, field: self
42
+ # If options are array don't need any pre-process
43
+ return value if options.is_a?(Array)
44
+
45
+ # If options are enum and display_value is true we return the Value of that key-value pair, else return key of that key-value pair
46
+ # WARNING: value here is the DB stored value and not the value of a key-value pair.
47
+ if enum.present?
48
+ return enum[value] if display_value
49
+ return value
50
+ end
51
+
52
+ # When code arrive here it means options are Hash
53
+ # If display_value is true we only need to return the value stored in DB
54
+ display_value ? value : options.invert[value]
55
+ end
46
56
 
47
- return value if display_value
57
+ private
48
58
 
49
- computed_options.invert.stringify_keys[value]
50
- elsif enum.present?
51
- if display_value
52
- options[value]
53
- else
54
- value
55
- end
56
- elsif display_value
57
- value
59
+ # Cache options as options given on block or as options received from arguments
60
+ def options
61
+ @options ||= if options_from_args.respond_to? :call
62
+ options_from_args.call model: model, resource: resource, view: view, field: self
58
63
  else
59
- options.invert.stringify_keys[value]
64
+ options_from_args
60
65
  end
61
66
  end
62
67
  end
@@ -10,6 +10,8 @@ module Avo
10
10
  class_attribute :template, default: "avo/base/select_filter"
11
11
  class_attribute :visible
12
12
 
13
+ attr_reader :arguments
14
+
13
15
  delegate :params, to: Avo::App
14
16
 
15
17
  class << self
@@ -24,6 +26,10 @@ module Avo
24
26
  end
25
27
  end
26
28
 
29
+ def initialize(arguments: {})
30
+ @arguments = arguments
31
+ end
32
+
27
33
  def apply_query(request, query, value)
28
34
  value.stringify_keys! if value.is_a? Hash
29
35
 
@@ -63,7 +69,8 @@ module Avo
63
69
  params: params,
64
70
  parent_model: parent_model,
65
71
  parent_resource: parent_resource,
66
- resource: resource
72
+ resource: resource,
73
+ arguments: arguments
67
74
  ).handle
68
75
 
69
76
  end
@@ -4,6 +4,8 @@ module Avo
4
4
  option :parent_model
5
5
  option :parent_resource
6
6
  option :resource
7
+ option :arguments
8
+
7
9
  # View is optional because in filter context the view is always :index
8
10
  option :view, optional: true
9
11
  end
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "2.19.0" unless const_defined?(:VERSION)
2
+ VERSION = "2.20.0" unless const_defined?(:VERSION)
3
3
  end
@@ -6345,14 +6345,14 @@ trix-toolbar .trix-button-group:not(:first-child){
6345
6345
  z-index:20
6346
6346
  }
6347
6347
 
6348
- .z-40{
6349
- z-index:40
6350
- }
6351
-
6352
6348
  .z-\[100\]{
6353
6349
  z-index:100
6354
6350
  }
6355
6351
 
6352
+ .z-40{
6353
+ z-index:40
6354
+ }
6355
+
6356
6356
  .z-\[60\]{
6357
6357
  z-index:60
6358
6358
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avo
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.19.0
4
+ version: 2.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Marin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-11-08 00:00:00.000000000 Z
12
+ date: 2022-11-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -151,20 +151,6 @@ dependencies:
151
151
  - - ">="
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
- - !ruby/object:Gem::Dependency
155
- name: breadcrumbs_on_rails
156
- requirement: !ruby/object:Gem::Requirement
157
- requirements:
158
- - - ">="
159
- - !ruby/object:Gem::Version
160
- version: '0'
161
- type: :runtime
162
- prerelease: false
163
- version_requirements: !ruby/object:Gem::Requirement
164
- requirements:
165
- - - ">="
166
- - !ruby/object:Gem::Version
167
- version: '0'
168
154
  - !ruby/object:Gem::Dependency
169
155
  name: dry-initializer
170
156
  requirement: !ruby/object:Gem::Requirement
@@ -1648,6 +1634,7 @@ files:
1648
1634
  - app/javascript/js/controllers/toggle_panel_controller.js
1649
1635
  - app/javascript/js/helpers/cast_boolean.js
1650
1636
  - app/javascript/js/helpers/debounce_promise.js
1637
+ - app/views/avo/actions/keep_modal_open.turbo_stream.erb
1651
1638
  - app/views/avo/actions/show.html.erb
1652
1639
  - app/views/avo/associations/new.html.erb
1653
1640
  - app/views/avo/base/_boolean_filter.html.erb
@@ -1717,6 +1704,7 @@ files:
1717
1704
  - lib/avo/base_card.rb
1718
1705
  - lib/avo/base_resource.rb
1719
1706
  - lib/avo/base_resource_tool.rb
1707
+ - lib/avo/concerns/breadcrumbs.rb
1720
1708
  - lib/avo/concerns/can_replace_fields.rb
1721
1709
  - lib/avo/concerns/fetches_things.rb
1722
1710
  - lib/avo/concerns/filters_session_handler.rb