avo 2.2.2 → 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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/Gemfile.lock +7 -1
  4. data/app/assets/builds/avo.css +8840 -0
  5. data/app/assets/builds/avo.js +423 -0
  6. data/app/assets/builds/avo.js.map +7 -0
  7. data/app/components/avo/sidebar/base_item_component.rb +1 -1
  8. data/app/components/avo/sidebar/group_component.html.erb +17 -15
  9. data/app/components/avo/sidebar/heading_component.html.erb +1 -1
  10. data/app/components/avo/sidebar/heading_component.rb +3 -1
  11. data/app/components/avo/sidebar/section_component.html.erb +4 -4
  12. data/app/components/avo/sidebar_component.html.erb +1 -1
  13. data/app/components/avo/views/resource_index_component.html.erb +19 -21
  14. data/app/controllers/avo/actions_controller.rb +1 -1
  15. data/app/controllers/avo/application_controller.rb +5 -1
  16. data/app/controllers/avo/base_controller.rb +9 -1
  17. data/app/controllers/avo/debug_controller.rb +24 -0
  18. data/app/javascript/avo.js +13 -0
  19. data/app/javascript/js/controllers/copy_to_clipboard_controller.js +37 -0
  20. data/app/javascript/js/controllers/menu_controller.js +23 -18
  21. data/app/javascript/js/controllers.js +2 -0
  22. data/app/views/avo/debug/index.html.erb +87 -0
  23. data/config/routes.rb +4 -1
  24. data/lib/avo/app.rb +18 -0
  25. data/lib/avo/licensing/h_q.rb +53 -48
  26. data/lib/avo/licensing/license.rb +8 -0
  27. data/lib/avo/licensing/license_manager.rb +4 -0
  28. data/lib/avo/services/authorization_service.rb +8 -2
  29. data/lib/avo/version.rb +1 -1
  30. data/lib/generators/avo/templates/initializer/avo.tt +23 -3
  31. data/public/avo-assets/avo.css +18 -5
  32. data/public/avo-assets/avo.js +58 -58
  33. data/public/avo-assets/avo.js.map +3 -3
  34. metadata +10 -4
@@ -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}"
@@ -3,24 +3,26 @@
3
3
  data-controller="menu"
4
4
  data-menu-target="self"
5
5
  data-menu-key-param="<%= key %>"
6
- data-menu-collapsed-param="<%= collapsed ? 'collapsed' : 'expanded' %>"
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"
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>
23
- <div class="w-full space-y-1" data-menu-target="items">
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 %>
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 %>
26
28
  <% end %>
@@ -3,7 +3,7 @@
3
3
  <span class="min-w-[20px]"><%= icon %></span> <span><%= label %></span>
4
4
  </div>
5
5
  <% if collapsable %>
6
- <div class="cursor-pointer"
6
+ <div class="cursor-pointer <%= 'rotate-90' if collapsed %>"
7
7
  data-action="click->menu#triggerCollapse"
8
8
  data-menu-key-param="<%= key %>"
9
9
  data-menu-target="svg"
@@ -2,12 +2,14 @@
2
2
 
3
3
  class Avo::Sidebar::HeadingComponent < ViewComponent::Base
4
4
  attr_reader :collapsable
5
+ attr_reader :collapsed
5
6
  attr_reader :icon
6
7
  attr_reader :key
7
8
  attr_reader :label
8
9
 
9
- def initialize(label: nil, icon: nil, collapsable: false, key: nil)
10
+ def initialize(label: nil, icon: nil, collapsable: false, collapsed: false, key: nil)
10
11
  @collapsable = collapsable
12
+ @collapsed = collapsed
11
13
  @icon = icon
12
14
  @key = key
13
15
  @label = label
@@ -1,13 +1,13 @@
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"
5
5
  data-menu-key-param="<%= key %>"
6
- data-menu-collapsed-param="<%= collapsed ? 'collapsed' : 'expanded' %>"
6
+ data-menu-default-collapsed-state="<%= collapsed ? 'collapsed' : 'expanded' %>"
7
7
  <% end %>
8
8
  >
9
- <%= render Avo::Sidebar::HeadingComponent.new label: item.name, icon: helpers.svg(icon, class: 'h-5'), collapsable: item.collapsable, key: key %>
10
- <div class="w-full space-y-1" data-menu-target="items">
9
+ <%= render Avo::Sidebar::HeadingComponent.new label: item.name, icon: helpers.svg(icon, class: "h-5"), collapsable: item.collapsable, collapsed: collapsed, key: key %>
10
+ <div class="w-full space-y-1 <%= 'hidden' if collapsed %>" data-menu-target="items">
11
11
  <% items.each do |item| %>
12
12
  <%= render Avo::Sidebar::ItemSwitcherComponent.new item: item %>
13
13
  <% end %>
@@ -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
@@ -44,7 +44,15 @@ module Avo
44
44
  unless @index_params[:sort_by].eql? :created_at
45
45
  @query = @query.unscope(:order)
46
46
  end
47
- @query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
47
+
48
+ # Check if the sortable field option is actually a proc and we need to do a custom sort
49
+ field_id = @index_params[:sort_by].to_sym
50
+ field = @resource.get_field_definitions.find { |field| field.id == field_id }
51
+ if field&.sortable.is_a?(Proc)
52
+ @query = field.sortable.call(@query, @index_params[:sort_direction])
53
+ else
54
+ @query = @query.order("#{@resource.model_class.table_name}.#{@index_params[:sort_by]} #{@index_params[:sort_direction]}")
55
+ end
48
56
  end
49
57
 
50
58
  # Apply filters
@@ -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
@@ -84,3 +84,16 @@ document.addEventListener('turbo:submit-end', () => document.body.classList.remo
84
84
  document.addEventListener('turbo:before-cache', () => {
85
85
  document.querySelectorAll('[data-turbo-remove-before-cache]').forEach((element) => element.remove())
86
86
  })
87
+
88
+ window.Avo = window.Avo || { configuration: {} }
89
+
90
+ window.Avo.menus = {
91
+ resetCollapsedState() {
92
+ Array.from(document.querySelectorAll('[data-menu-key-param]'))
93
+ .map((i) => i.getAttribute('data-menu-key-param'))
94
+ .filter(Boolean)
95
+ .forEach((key) => {
96
+ window.localStorage.removeItem(key)
97
+ })
98
+ },
99
+ }
@@ -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
+ }
@@ -1,31 +1,46 @@
1
1
  import { Controller } from '@hotwired/stimulus'
2
- import isNull from 'lodash/isNull'
3
2
 
4
3
  export default class extends Controller {
5
4
  static targets = ['svg', 'items', 'self'];
6
5
 
7
- collapsed = false;
6
+ collapsed = true;
8
7
 
9
8
  get key() {
10
9
  return this.selfTarget.getAttribute('data-menu-key-param')
11
10
  }
12
11
 
13
- defaultState() {
14
- return this.selfTarget.getAttribute('data-menu-collapsed-param') === 'collapsed'
12
+ get defaultState() {
13
+ return this.selfTarget.getAttribute('data-menu-default-collapsed-state')
14
+ }
15
+
16
+ get userState() {
17
+ return window.localStorage.getItem(this.key)
18
+ }
19
+
20
+ set userState(payload) {
21
+ window.localStorage.setItem(this.key, payload)
22
+ }
23
+
24
+ get initiallyCollapsed() {
25
+ if (this.userState === 'collapsed' || this.defaultState === 'collapsed') return true
26
+ if (this.userState === 'expanded' || this.defaultState === 'expanded') return false
27
+
28
+ return false
15
29
  }
16
30
 
17
31
  connect() {
18
- if (this.getState() === 'collapsed') {
19
- this.collapsed = true
20
- this.markCollapsed()
21
- } else if (isNull(this.getState()) && this.defaultState()) {
32
+ if (this.initiallyCollapsed) {
22
33
  this.collapsed = true
23
34
  this.markCollapsed()
35
+ } else {
36
+ this.collapsed = false
37
+ this.markExpanded()
24
38
  }
25
39
  }
26
40
 
27
41
  triggerCollapse() {
28
42
  this.collapsed = !this.collapsed
43
+ this.userState = this.collapsed ? 'collapsed' : 'expanded'
29
44
 
30
45
  this.updateDom()
31
46
  }
@@ -41,20 +56,10 @@ export default class extends Controller {
41
56
  markCollapsed() {
42
57
  this.svgTarget.classList.add('rotate-90')
43
58
  this.itemsTarget.classList.add('hidden')
44
- this.storeState('collapsed')
45
59
  }
46
60
 
47
61
  markExpanded() {
48
62
  this.svgTarget.classList.remove('rotate-90')
49
63
  this.itemsTarget.classList.remove('hidden')
50
- this.storeState('expanded')
51
- }
52
-
53
- getState() {
54
- return window.localStorage.getItem(this.key)
55
- }
56
-
57
- storeState(payload) {
58
- window.localStorage.setItem(this.key, payload)
59
64
  }
60
65
  }
@@ -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)
@@ -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
@@ -13,13 +13,62 @@ module Avo
13
13
  end
14
14
 
15
15
  def response
16
- @hq_response || request
16
+ make_request
17
+ end
18
+
19
+ def fresh_response
20
+ make_request(fresh: true)
21
+ end
22
+
23
+ def payload
24
+ {
25
+ license: Avo.configuration.license,
26
+ license_key: Avo.configuration.license_key,
27
+ avo_version: Avo::VERSION,
28
+ rails_version: Rails::VERSION::STRING,
29
+ ruby_version: RUBY_VERSION,
30
+ environment: Rails.env,
31
+ ip: current_request.ip,
32
+ host: current_request.host,
33
+ port: current_request.port,
34
+ app_name: app_name
35
+ }
36
+ end
37
+
38
+ def avo_metadata
39
+ resources = App.resources
40
+ field_definitions = resources.map(&:get_field_definitions)
41
+ fields_count = field_definitions.map(&:count).sum
42
+ fields_per_resource = sprintf("%0.01f", fields_count / (resources.count + 0.0))
43
+
44
+ field_types = {}
45
+ custom_fields_count = 0
46
+ field_definitions.each do |fields|
47
+ fields.each do |field|
48
+ field_types[field.type] ||= 0
49
+ field_types[field.type] += 1
50
+
51
+ custom_fields_count += 1 if field.custom?
52
+ end
53
+ end
54
+
55
+ {
56
+ resources_count: resources.count,
57
+ fields_count: fields_count,
58
+ fields_per_resource: fields_per_resource,
59
+ custom_fields_count: custom_fields_count,
60
+ field_types: field_types,
61
+ **other_metadata(:actions),
62
+ **other_metadata(:filters),
63
+ main_menu_present: Avo::App.main_menu.present?,
64
+ profile_menu_present: Avo::App.profile_menu.present?,
65
+ }
17
66
  end
18
67
 
19
68
  private
20
69
 
21
- def request
22
- return cached_response if has_cached_response
70
+ def make_request(fresh: false)
71
+ return cached_response if has_cached_response && !fresh
23
72
 
24
73
  begin
25
74
  perform_and_cache_request
@@ -51,13 +100,12 @@ module Avo
51
100
  def cache_response(time, response)
52
101
  response.merge!(
53
102
  expiry: time,
103
+ fetched_at: Time.now,
54
104
  **payload
55
105
  ).stringify_keys!
56
106
 
57
107
  @cache_store.write(CACHE_KEY, response, expires_in: time)
58
108
 
59
- @hq_response = response
60
-
61
109
  response
62
110
  end
63
111
 
@@ -67,55 +115,12 @@ module Avo
67
115
  HTTParty.post ENDPOINT, body: payload.to_json, headers: {'Content-type': "application/json"}, timeout: REQUEST_TIMEOUT
68
116
  end
69
117
 
70
- def payload
71
- {
72
- license: Avo.configuration.license,
73
- license_key: Avo.configuration.license_key,
74
- avo_version: Avo::VERSION,
75
- rails_version: Rails::VERSION::STRING,
76
- ruby_version: RUBY_VERSION,
77
- environment: Rails.env,
78
- ip: current_request.ip,
79
- host: current_request.host,
80
- port: current_request.port,
81
- app_name: app_name
82
- }
83
- end
84
-
85
118
  def app_name
86
119
  Rails.application.class.to_s.split("::").first
87
120
  rescue
88
121
  nil
89
122
  end
90
123
 
91
- def avo_metadata
92
- resources = App.resources
93
- field_definitions = resources.map(&:get_field_definitions)
94
- fields_count = field_definitions.map(&:count).sum
95
- fields_per_resource = sprintf("%0.01f", fields_count / (resources.count + 0.0))
96
-
97
- field_types = {}
98
- custom_fields_count = 0
99
- field_definitions.each do |fields|
100
- fields.each do |field|
101
- field_types[field.type] ||= 0
102
- field_types[field.type] += 1
103
-
104
- custom_fields_count += 1 if field.custom?
105
- end
106
- end
107
-
108
- {
109
- resources_count: resources.count,
110
- fields_count: fields_count,
111
- fields_per_resource: fields_per_resource,
112
- custom_fields_count: custom_fields_count,
113
- field_types: field_types,
114
- **other_metadata(:actions),
115
- **other_metadata(:filters),
116
- }
117
- end
118
-
119
124
  def other_metadata(type = :actions)
120
125
  resources = App.resources
121
126
 
@@ -59,6 +59,14 @@ module Avo
59
59
  def lacks_with_trial(ability)
60
60
  !has_with_trial ability
61
61
  end
62
+
63
+ def name
64
+ if id.present?
65
+ id.humanize
66
+ else
67
+ self.class.to_s.split('::').last.underscore.humanize.gsub ' license', ''
68
+ end
69
+ end
62
70
  end
63
71
  end
64
72
  end