avo 2.3.1.pre.1 → 2.3.1.pre.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -1
  3. data/app/assets/builds/avo.css +18 -5
  4. data/app/assets/builds/avo.js +58 -58
  5. data/app/assets/builds/avo.js.map +3 -3
  6. data/app/components/avo/sidebar/base_item_component.rb +1 -1
  7. data/app/components/avo/sidebar/group_component.html.erb +15 -13
  8. data/app/components/avo/sidebar/section_component.html.erb +1 -1
  9. data/app/components/avo/sidebar_component.html.erb +1 -1
  10. data/app/components/avo/views/resource_index_component.html.erb +19 -21
  11. data/app/controllers/avo/actions_controller.rb +1 -1
  12. data/app/controllers/avo/application_controller.rb +5 -1
  13. data/app/controllers/avo/base_controller.rb +9 -14
  14. data/app/controllers/avo/debug_controller.rb +24 -0
  15. data/app/javascript/js/controllers/copy_to_clipboard_controller.js +37 -0
  16. data/app/javascript/js/controllers/filter_controller.js +1 -11
  17. data/app/javascript/js/controllers/multiple_select_filter_controller.js +1 -3
  18. data/app/javascript/js/controllers/select_filter_controller.js +1 -1
  19. data/app/javascript/js/controllers.js +2 -0
  20. data/app/views/avo/base/_boolean_filter.html.erb +14 -1
  21. data/app/views/avo/base/_multiple_select_filter.html.erb +13 -2
  22. data/app/views/avo/base/_select_filter.html.erb +9 -1
  23. data/app/views/avo/base/_text_filter.html.erb +10 -2
  24. data/app/views/avo/debug/index.html.erb +87 -0
  25. data/config/routes.rb +4 -1
  26. data/lib/avo/app.rb +18 -0
  27. data/lib/avo/filters/base_filter.rb +2 -17
  28. data/lib/avo/filters/boolean_filter.rb +0 -12
  29. data/lib/avo/filters/multiple_select_filter.rb +0 -15
  30. data/lib/avo/filters/text_filter.rb +0 -4
  31. data/lib/avo/licensing/h_q.rb +53 -48
  32. data/lib/avo/licensing/license.rb +8 -0
  33. data/lib/avo/licensing/license_manager.rb +4 -0
  34. data/lib/avo/version.rb +1 -1
  35. data/lib/generators/avo/templates/initializer/avo.tt +23 -3
  36. data/public/avo-assets/avo.css +18 -5
  37. data/public/avo-assets/avo.js +58 -58
  38. data/public/avo-assets/avo.js.map +3 -3
  39. metadata +5 -2
@@ -12,7 +12,7 @@ class Avo::Sidebar::BaseItemComponent < ViewComponent::Base
12
12
  end
13
13
 
14
14
  def key
15
- result = "avo.#{request.host}.main_menu.#{item.name.underscore}"
15
+ result = "avo.#{request.host}.main_menu.#{item.name.to_s.underscore}"
16
16
 
17
17
  if item.icon.present?
18
18
  result += ".#{item.icon.parameterize.underscore}"
@@ -6,20 +6,22 @@
6
6
  data-menu-default-collapsed-state="<%= collapsed ? 'collapsed' : 'expanded' %>"
7
7
  <% end %>
8
8
  >
9
- <div class="flex justify-between px-10 pt-2 pb-0 text-gray-600 ">
10
- <div class="flex items-center text-xs uppercase font-semibold leading-none">
11
- <%= item.name %>
12
- </div>
13
- <% if collapsable %>
14
- <div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
15
- data-action="click->menu#triggerCollapse"
16
- data-menu-target="svg"
17
- data-menu-key-param="<%= key %>"
18
- >
19
- <%= helpers.svg 'heroicons/outline/chevron-down', class: "h-4"%>
9
+ <% if item.name.present? %>
10
+ <div class="flex justify-between px-10 pt-2 pb-0 text-gray-600 ">
11
+ <div class="flex items-center text-xs uppercase font-semibold leading-none">
12
+ <%= item.name %>
20
13
  </div>
21
- <% end %>
22
- </div>
14
+ <% if collapsable %>
15
+ <div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
16
+ data-action="click->menu#triggerCollapse"
17
+ data-menu-target="svg"
18
+ data-menu-key-param="<%= key %>"
19
+ >
20
+ <%= helpers.svg 'heroicons/outline/chevron-down', class: "h-4"%>
21
+ </div>
22
+ <% end %>
23
+ </div>
24
+ <% end %>
23
25
  <div class="w-full space-y-1 <%= 'hidden' if collapsed %>" data-menu-target="items">
24
26
  <% items.each do |item| %>
25
27
  <%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
@@ -1,4 +1,4 @@
1
- <div class="space-y-1"
1
+ <div class="space-y-1 mb-4"
2
2
  <% if collapsable %>
3
3
  data-controller="menu"
4
4
  data-menu-target="self"
@@ -11,7 +11,7 @@
11
11
  <%= 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
12
 
13
13
  <% if Avo::App.license.has_with_trial(:menu_editor) && Avo.configuration.main_menu.present? %>
14
- <div class="text-black space-y-4">
14
+ <div class="text-black">
15
15
  <% Avo::App.main_menu.items.each do |item| %>
16
16
  <%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
17
17
  <% end %>
@@ -16,27 +16,25 @@
16
16
  <% end %>
17
17
  <% end %>
18
18
  <% c.body do %>
19
- <% if @resource.search_query.present? || @filters.present? || available_view_types.count > 1 %>
20
- <div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4"
21
- data-selected-resources-name="<%= @resource.model_key %>"
22
- data-selected-resources="[]"
23
- >
24
- <% if @resource.search_query.present? %>
25
- <div class="flex items-center px-4 w-64">
26
- <%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.model_key} %>
27
- </div>
28
- <% else %>
29
- <%# Offset for the space-y-2 property when the serach is missing %>
30
- <div class="-mb-2"></div>
31
- <% end %>
32
- <% if @filters.present? || available_view_types.count > 1 %>
33
- <div class="justify-self-end flex justify-start xs:justify-end items-center px-4 space-x-3">
34
- <%= render Avo::FiltersComponent.new filters: @filters, resource: @resource %>
35
- <%= 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 %>
36
- </div>
37
- <% end %>
38
- </div>
39
- <% end %>
19
+ <div class="flex flex-col xs:flex-row xs:justify-between space-y-2 xs:space-y-0 py-4 <%= 'hidden' if @resource.search_query.nil? && @filters.empty? && available_view_types.count <= 1 %>"
20
+ data-selected-resources-name="<%= @resource.model_key %>"
21
+ data-selected-resources="[]"
22
+ >
23
+ <% if @resource.search_query.present? %>
24
+ <div class="flex items-center px-4 w-64">
25
+ <%= render partial: 'avo/partials/resource_search', locals: {resource: @resource.model_key} %>
26
+ </div>
27
+ <% else %>
28
+ <%# Offset for the space-y-2 property when the serach is missing %>
29
+ <div class="-mb-2"></div>
30
+ <% end %>
31
+ <% if @filters.present? || available_view_types.count > 1 %>
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 %>
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
+ </div>
36
+ <% end %>
37
+ </div>
40
38
  <% if view_type.to_sym == :table %>
41
39
  <% if @resources.present? %>
42
40
  <div class="w-full overflow-auto flex flex-col mt-0">
@@ -34,7 +34,7 @@ module Avo
34
34
  private
35
35
 
36
36
  def action_params
37
- params.permit(:resource_name, :action_id, fields: {})
37
+ params.permit(:authenticity_token, :resource_name, :action_id, fields: {})
38
38
  end
39
39
 
40
40
  def set_action
@@ -275,8 +275,12 @@ module Avo
275
275
  request.original_url.match?(/.*#{Avo::App.root_path}\/dashboards\/.*/)
276
276
  end
277
277
 
278
+ def on_debug_path
279
+ request.original_url.match?(/.*#{Avo::App.root_path}\/avo_private\/debug.*/)
280
+ end
281
+
278
282
  def on_custom_tool_page
279
- !(on_root_path || on_resources_path || on_api_path || on_dashboards_path)
283
+ !(on_root_path || on_resources_path || on_api_path || on_dashboards_path || on_debug_path)
280
284
  end
281
285
 
282
286
  def model_param_key
@@ -5,7 +5,6 @@ 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
9
8
  before_action :set_model, only: [:show, :edit, :destroy, :update, :order]
10
9
  before_action :set_model_to_fill
11
10
  before_action :set_edit_title_and_breadcrumbs, only: [:edit, :update]
@@ -56,8 +55,8 @@ module Avo
56
55
  end
57
56
  end
58
57
 
59
- # Apply filters to the current query
60
- filters_to_be_applied.each do |filter_class, filter_value|
58
+ # Apply filters
59
+ applied_filters.each do |filter_class, filter_value|
61
60
  @query = filter_class.safe_constantize.new.apply_query request, @query, filter_value
62
61
  end
63
62
 
@@ -309,32 +308,28 @@ module Avo
309
308
  .select { |action| action.visible_in_view }
310
309
  end
311
310
 
312
- def set_applied_filters
313
- @applied_filters = JSON.parse(Base64.decode64(params[:filters]))
314
- rescue
315
- @applied_filters = {}
316
- end
311
+ def applied_filters
312
+ if params[:filters].present?
313
+ return JSON.parse(Base64.decode64(params[:filters]))
314
+ end
317
315
 
318
- # Get the default state of the filters and override with the user applied filters
319
- def filters_to_be_applied
320
316
  filter_defaults = {}
321
317
 
322
318
  @resource.get_filters.each do |filter_class|
323
319
  filter = filter_class.new
324
320
 
325
- unless filter.default.nil?
321
+ if filter.default.present?
326
322
  filter_defaults[filter_class.to_s] = filter.default
327
323
  end
328
324
  end
329
325
 
330
- filter_defaults.merge(@applied_filters)
326
+ filter_defaults
331
327
  end
332
328
 
333
- # Caching these so we know when the filters have changed so we reset the pagination
334
329
  def cache_applied_filters
335
330
  ::Avo::App.cache_store.delete applied_filters_cache_key if params[:filters].nil?
336
331
 
337
- ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 1.day)
332
+ ::Avo::App.cache_store.write(applied_filters_cache_key, params[:filters], expires_in: 7.days)
338
333
  end
339
334
 
340
335
  def reset_pagination_if_filters_changed
@@ -0,0 +1,24 @@
1
+ require_dependency "avo/application_controller"
2
+
3
+ module Avo
4
+ class DebugController < ApplicationController
5
+ def index
6
+ end
7
+
8
+ def refresh_license
9
+ license = Licensing::LicenseManager.refresh_license request
10
+
11
+ if license.valid?
12
+ flash[:notice] = "avohq.io responded: \"#{license.id.humanize} license is valid\"."
13
+ else
14
+ if license.response['reason'].present?
15
+ flash[:error] = "avohq.io responded: \"#{license.response['reason']}\"."
16
+ else
17
+ flash[:error] = license.response['error']
18
+ end
19
+ end
20
+
21
+ redirect_back fallback_location: avo.avo_private_debug_index_path
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ import { Controller } from '@hotwired/stimulus'
2
+
3
+ export default class extends Controller {
4
+ copy() {
5
+ const str = this.context.element.dataset.text
6
+ /* ——— Derived from: https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
7
+ improved to add iOS device compatibility——— */
8
+ const el = document.createElement('textarea') // Create a <textarea> element
9
+
10
+ const storeContentEditable = el.contentEditable
11
+ const storeReadOnly = el.readOnly
12
+
13
+ el.value = str // Set its value to the string that you want copied
14
+ el.contentEditable = true
15
+ el.readOnly = false
16
+ el.setAttribute('readonly', false) // Make it readonly false for iOS compatability
17
+ el.setAttribute('contenteditable', true) // Make it editable for iOS
18
+ el.style.position = 'absolute'
19
+ el.style.left = '-9999px' // Move outside the screen to make it invisible
20
+ document.body.appendChild(el) // Append the <textarea> element to the HTML document
21
+ const selected = document.getSelection().rangeCount > 0 // Check if there is any content selected previously
22
+ ? document.getSelection().getRangeAt(0) // Store selection if found
23
+ : false // Mark as false to know no selection existed before
24
+ el.select() // Select the <textarea> content
25
+ el.setSelectionRange(0, 999999)
26
+ document.execCommand('copy') // Copy - only works as a result of a user action (e.g. click events)
27
+ document.body.removeChild(el) // Remove the <textarea> element
28
+ if (selected) {
29
+ // If a selection existed before copying
30
+ document.getSelection().removeAllRanges() // Unselect everything on the HTML document
31
+ document.getSelection().addRange(selected) // Restore the original selection
32
+ }
33
+
34
+ el.contentEditable = storeContentEditable
35
+ el.readOnly = storeReadOnly
36
+ }
37
+ }
@@ -38,22 +38,18 @@ export default class extends Controller {
38
38
  const value = this.getFilterValue()
39
39
  const filterClass = this.getFilterClass()
40
40
 
41
- // Get the `filters` param for all params
42
41
  let filters = this.uriParams()[this.uriParam('filters')]
43
42
 
44
- // Decode the filters
45
43
  if (filters) {
46
44
  filters = JSON.parse(this.b64DecodeUnicode(filters))
47
45
  } else {
48
46
  filters = {}
49
47
  }
50
48
 
51
- // Get the values for this particular filter
52
49
  filters[filterClass] = value
53
50
 
54
51
  const filtered = Object.keys(filters)
55
- // Filter out the filters without a value
56
- .filter((key) => filters[key] !== null)
52
+ .filter((key) => filters[key] !== '')
57
53
  .reduce((obj, key) => {
58
54
  obj[key] = filters[key]
59
55
 
@@ -62,16 +58,10 @@ export default class extends Controller {
62
58
 
63
59
  let encodedFilters
64
60
 
65
- // Encode the filters and their values
66
61
  if (filtered && Object.keys(filtered).length > 0) {
67
62
  encodedFilters = this.b64EncodeUnicode(JSON.stringify(filtered))
68
63
  }
69
64
 
70
- this.navigateToURLWithFilters(encodedFilters)
71
- }
72
-
73
- navigateToURLWithFilters(encodedFilters) {
74
- // Create a new URI with them
75
65
  const url = new URI(this.urlRedirectTarget.href)
76
66
 
77
67
  const query = {
@@ -4,9 +4,7 @@ export default class extends BaseFilterController {
4
4
  static targets = ['selector']
5
5
 
6
6
  getFilterValue() {
7
- const filterValue = Array.from(this.selectorTarget.selectedOptions).map(({ value }) => value)
8
-
9
- return filterValue.length === 0 ? null : filterValue
7
+ return Array.from(this.selectorTarget.selectedOptions).map(({ value }) => value)
10
8
  }
11
9
 
12
10
  getFilterClass() {
@@ -4,7 +4,7 @@ export default class extends BaseFilterController {
4
4
  static targets = ['selector']
5
5
 
6
6
  getFilterValue() {
7
- return this.selectorTarget.value === '' ? null : this.selectorTarget.value
7
+ return this.selectorTarget.value
8
8
  }
9
9
 
10
10
  getFilterClass() {
@@ -7,6 +7,7 @@ import AttachmentsController from './controllers/attachments_controller'
7
7
  import BelongsToFieldController from './controllers/fields/belongs_to_field_controller'
8
8
  import BooleanFilterController from './controllers/boolean_filter_controller'
9
9
  import CodeFieldController from './controllers/fields/code_field_controller'
10
+ import CopyToClipboardController from './controllers/copy_to_clipboard_controller'
10
11
  import DashboardCardController from './controllers/dashboard_card_controller'
11
12
  import DateFieldController from './controllers/fields/date_field_controller'
12
13
  import FilterController from './controllers/filter_controller'
@@ -34,6 +35,7 @@ application.register('actions-picker', ActionsPickerController)
34
35
  application.register('alerts', AlertsController)
35
36
  application.register('attachments', AttachmentsController)
36
37
  application.register('boolean-filter', BooleanFilterController)
38
+ application.register('copy-to-clipboard', CopyToClipboardController)
37
39
  application.register('dashboard-card', DashboardCardController)
38
40
  application.register('filter', FilterController)
39
41
  application.register('hidden-input', HiddenInputController)
@@ -1,10 +1,23 @@
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
+ %>
1
14
  <div data-controller="boolean-filter" data-filter-name="<%= filter.name %>">
2
15
  <%= filter_wrapper name: filter.name do %>
3
16
  <div class="flex items-center">
4
17
  <div class="space-y-2">
5
18
  <% filter.options.each do |value, label| %>
6
19
  <label class="flex items-center text-gray-700 text-sm">
7
- <%= check_box_tag filter.id, value, filter.selected_value(value.to_s, @applied_filters),
20
+ <%= check_box_tag filter.id, value, set_value[value.to_s],
8
21
  class: 'mr-2 text-lg h-4 w-4',
9
22
  id: "avo_filters_#{filter.id.parameterize.underscore}",
10
23
  'data-filter-class': filter.class,
@@ -1,6 +1,17 @@
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
+ %>
1
12
  <div data-controller="multiple-select-filter" data-filter-name="<%= filter.name %>">
2
13
  <%= filter_wrapper name: filter.name do %>
3
- <%= select_tag filter.id, options_for_select(filter.options.invert, filter.selected_value(@applied_filters)),
14
+ <%= select_tag filter.id, options_for_select(filter.options.invert, set_value),
4
15
  class: input_classes('w-full mb-0'),
5
16
  multiple: true,
6
17
  id: "avo_filters_#{filter.id.parameterize.underscore}",
@@ -8,7 +19,7 @@
8
19
  'data-multiple-select-filter-target': 'selector'
9
20
  %>
10
21
  <div class="flex justify-end">
11
- <%= a_button class: 'mt-4', color: :blue, size: :xs, data: { action: "multiple-select-filter#changeFilter" } do %>
22
+ <%= a_button class: 'mt-4', color: :blue, size: :sm, data: { action: "multiple-select-filter#changeFilter" } do %>
12
23
  Filter by <%=filter.name %>
13
24
  <% end %>
14
25
  </div>
@@ -1,6 +1,14 @@
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
+ %>
1
9
  <div data-controller="select-filter" data-filter-name="<%= filter.name %>">
2
10
  <%= filter_wrapper name: filter.name do %>
3
- <%= select_tag filter.id, options_for_select(filter.options.invert, filter.applied_or_default_value(@applied_filters)),
11
+ <%= select_tag filter.id, options_for_select(filter.options.invert, set_value),
4
12
  class: input_classes('w-full mb-0'),
5
13
  include_blank: '—',
6
14
  id: "avo_filters_#{filter.id.parameterize.underscore}",
@@ -1,6 +1,14 @@
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
+ %>
1
9
  <div data-controller="text-filter" data-filter-name="<%= filter.name %>">
2
10
  <%= filter_wrapper name: filter.name do %>
3
- <%= text_field_tag filter.id, filter.applied_or_default_value(@applied_filters),
11
+ <%= text_field_tag filter.id, set_value,
4
12
  class: input_classes('w-full mb-0'),
5
13
  id: "avo_filters_#{filter.id.parameterize.underscore}",
6
14
  'data-filter-class': filter.class.to_s,
@@ -8,7 +16,7 @@
8
16
  'data-action': 'keypress->text-filter#tryToSubmit'
9
17
  %>
10
18
  <div class="flex justify-end">
11
- <%= a_button class: 'mt-4', color: :blue, data: { action: "text-filter#changeFilter" }, size: :xs do %>
19
+ <%= a_button class: 'mt-4', color: :blue, data: { action: "text-filter#changeFilter" }, size: :sm do %>
12
20
  <%= filter.button_label || "Filter by #{filter.name}" %>
13
21
  <% end %>
14
22
  </div>
@@ -0,0 +1,87 @@
1
+ <%
2
+ license = Avo::App.license
3
+ information_items = [
4
+ 'license',
5
+ 'license_key',
6
+ 'ruby_version',
7
+ 'rails_version',
8
+ 'environment',
9
+ 'host',
10
+ 'port',
11
+ 'ip',
12
+ 'app_name',
13
+ ]
14
+ hq_payload = Avo::Licensing::HQ.new(request).payload
15
+ %>
16
+ <div class="flex flex-col">
17
+ <%= render Avo::PanelComponent.new title: 'Debug Avo', description: 'Use this page to debug the Avo license.' do |c| %>
18
+ <% c.tools do %>
19
+ <% end %>
20
+ <% c.bare_content do %>
21
+ <div class="grid gap-4 sm:grid-cols-3">
22
+ <div class="relative flex flex-col bg-white rounded shadow-panel p-4 space-y-4 h-full col-span-1">
23
+ <div class="font-semibold">License info</div>
24
+ <div class="flex flex-col justify-between flex-1">
25
+ <div>
26
+ <div class="text-xl font-semibold"><%= license.name %></div>
27
+ <% if license.response['reason'] %>
28
+ <dl>
29
+ <dt class="font-semibold text-sm">Error</dt>
30
+ <dd class="text-red-600"><%= license.response['reason'] %></dd>
31
+ </dl>
32
+ <% end %>
33
+ <% if license.response['error'] %>
34
+ <dl>
35
+ <dt class="font-semibold text-sm">Error</dt>
36
+ <dd class="text-red-600"><%= license.response['error'] %></dd>
37
+ <dt class="font-semibold text-sm">Exception message</dt>
38
+ <dd class="text-red-600"><%= license.response['exception_message'] %></dd>
39
+ </dl>
40
+ <% end %>
41
+ <div class="mt-4">
42
+ <dl>
43
+ <% information_items.each do |item| %>
44
+ <dt class="font-semibold text-sm"><%= item.humanize %></dt>
45
+ <dd><%= license.response[item] %></dd>
46
+ <% end %>
47
+ <dt class="font-semibold text-sm">Last fetched at</dt>
48
+ <dd><%= license.response['fetched_at'] %> <br>
49
+ (<%= time_ago_in_words license.response['fetched_at'] %> ago)</dd>
50
+ <dt class="font-semibold text-sm">Cache store</dt>
51
+ <dd><%= Avo::App.cache_store.class.to_s %> - <%= Avo::App.cache_store.options.inspect %></dd>
52
+ </dl>
53
+ </div>
54
+ </div>
55
+ <div class="flex justify-end mt-4">
56
+ <%= a_button style: :outline,
57
+ color: :blue,
58
+ url: '/admin/avo_private/debug/refresh_license',
59
+ method: :post,
60
+ loading: true,
61
+ icon: 'heroicons/outline/refresh' do %>
62
+ Refresh license
63
+ <% end %>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="relative bg-white rounded shadow-panel p-4 space-y-4 col-span-2">
68
+ <div class="font-semibold">Debug report</div>
69
+ <div>
70
+ <%== ap Avo::App.debug_report(request) %>
71
+ </div>
72
+ <div class="flex justify-end">
73
+ <%= a_button icon: 'heroicons/outline/clipboard',
74
+ style: :primary,
75
+ data: {
76
+ controller: 'copy-to-clipboard',
77
+ action: "click->copy-to-clipboard#copy",
78
+ text: Avo::App.debug_report(request),
79
+ } do %>
80
+ Copy debug info
81
+ <% end %>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ <% end %>
86
+ <% end %>
87
+ </div>
data/config/routes.rb CHANGED
@@ -39,8 +39,11 @@ Avo::Engine.routes.draw do
39
39
  delete "/:resource_name/:id/:related_name/:related_id", to: "associations#destroy", as: "associations_destroy"
40
40
  end
41
41
 
42
+ get "/debug", to: "debug#index", as: "debug_index"
43
+ post "/debug/refresh_license", to: "debug#refresh_license"
44
+
42
45
  if Rails.env.development? or Rails.env.staging?
43
- scope "avo_private", as: "avo_private" do
46
+ scope "/avo_private", as: "avo_private" do
44
47
  get "/design", to: "private#design"
45
48
  end
46
49
  end
data/lib/avo/app.rb CHANGED
@@ -115,6 +115,24 @@ module Avo
115
115
 
116
116
  Avo::Menu::Builder.parse_menu(&Avo.configuration.profile_menu)
117
117
  end
118
+
119
+ def debug_report(request)
120
+ payload = {}
121
+ hq = Avo::Licensing::HQ.new(request)
122
+
123
+ payload[:hq_payload] = hq.payload
124
+ payload[:license_id] = Avo::App.license.id
125
+ payload[:license_valid] = Avo::App.license.valid?
126
+ payload[:license_payload] = Avo::App.license.payload
127
+ payload[:license_response] = Avo::App.license.response
128
+ payload[:cache_store] = self.cache_store.class.to_s
129
+ payload[:avo_metadata] = hq.avo_metadata
130
+ payload[:app_timezone] = Time.now.zone
131
+
132
+ payload
133
+ rescue => e
134
+ e
135
+ end
118
136
  end
119
137
  end
120
138
  end
@@ -3,11 +3,11 @@ module Avo
3
3
  class BaseFilter
4
4
  class_attribute :name, default: "Filter"
5
5
  class_attribute :component, default: "boolean-filter"
6
- class_attribute :default, default: nil
6
+ class_attribute :default, default: ""
7
7
  class_attribute :template, default: "avo/base/select_filter"
8
8
 
9
9
  def apply_query(request, query, value)
10
- value.stringify_keys! if value.is_a? Hash
10
+ value.symbolize_keys! if value.is_a? Hash
11
11
 
12
12
  apply(request, query, value)
13
13
  end
@@ -15,21 +15,6 @@ module Avo
15
15
  def id
16
16
  self.class.name.underscore.tr("/", "_")
17
17
  end
18
-
19
- # Get the applied value this filter.
20
- # If it's not present return the default value.
21
- def applied_or_default_value(applied_filters)
22
- # Get the values for this particular filter
23
- applied_value = applied_filters[self.class.to_s]
24
-
25
- # Return that value if present
26
- return applied_value unless applied_value.nil?
27
-
28
- # Return that default
29
- default
30
- rescue
31
- default
32
- end
33
18
  end
34
19
  end
35
20
  end
@@ -2,18 +2,6 @@ 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
17
5
  end
18
6
  end
19
7
  end
@@ -2,21 +2,6 @@ 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
20
5
  end
21
6
  end
22
7
  end