avo 1.22.1 → 1.22.4

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/app/components/avo/navigation_link_component.rb +1 -1
  4. data/app/controllers/avo/application_controller.rb +7 -0
  5. data/app/controllers/avo/base_controller.rb +0 -1
  6. data/app/javascript/js/application.js +1 -1
  7. data/app/javascript/js/controllers/action_controller.js +1 -1
  8. data/app/javascript/js/controllers/actions_picker_controller.js +1 -1
  9. data/app/javascript/js/controllers/alerts_controller.js +1 -1
  10. data/app/javascript/js/controllers/attachments_controller.js +1 -1
  11. data/app/javascript/js/controllers/fields/belongs_to_field_controller.js +1 -1
  12. data/app/javascript/js/controllers/fields/code_field_controller.js +1 -1
  13. data/app/javascript/js/controllers/fields/date_field_controller.js +1 -1
  14. data/app/javascript/js/controllers/fields/key_value_controller.js +3 -3
  15. data/app/javascript/js/controllers/fields/simple_mde_controller.js +1 -1
  16. data/app/javascript/js/controllers/fields/trix_field_controller.js +1 -1
  17. data/app/javascript/js/controllers/filter_controller.js +3 -7
  18. data/app/javascript/js/controllers/hidden_input_controller.js +1 -1
  19. data/app/javascript/js/controllers/item_select_all_controller.js +4 -4
  20. data/app/javascript/js/controllers/item_selector_controller.js +1 -1
  21. data/app/javascript/js/controllers/loading_button_controller.js +1 -1
  22. data/app/javascript/js/controllers/modal_controller.js +10 -1
  23. data/app/javascript/js/controllers/per_page_controller.js +1 -1
  24. data/app/javascript/js/controllers/search_controller.js +7 -2
  25. data/app/javascript/js/controllers/tippy_controller.js +1 -1
  26. data/app/javascript/js/controllers/toggle_panel_controller.js +1 -1
  27. data/app/views/avo/actions/show.html.erb +7 -3
  28. data/db/factories.rb +2 -2
  29. data/lib/avo/base_action.rb +21 -0
  30. data/lib/avo/base_resource.rb +18 -12
  31. data/lib/avo/fields/belongs_to_field.rb +9 -0
  32. data/lib/avo/licensing/h_q.rb +55 -1
  33. data/lib/avo/version.rb +1 -1
  34. data/public/avo-assets/avo.js +2569 -1926
  35. data/public/avo-assets/avo.js.map +3 -3
  36. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '04783e6660bd1ebada3544318b2f48082e3e43eb4bc7ee3df2986a9951d04090'
4
- data.tar.gz: 6e997ae6e0dbb4eaa7828b669b73efd811370c0cfb5213d405f15bdd41bb9240
3
+ metadata.gz: 472b0c4d748a2cc35c595170f3abd2c83e8ef62ea2f41ec15ba2fac1b4fba4eb
4
+ data.tar.gz: 28de6b788064fb476e80234f058e192dae5bc1866bb544c7db197a53adf801dd
5
5
  SHA512:
6
- metadata.gz: d61380b2f4c216d4ff1bd4e3e2f882500ba5e09835712d0661e8a79afd505f4b308ab7f30b41fcd95750e061003ca79a25b826e94eb9d007ba786923ce18740c
7
- data.tar.gz: 383afdfdc9eaaa92e00b6b91895a1b14ebff2d1655e3c605e39838e6640e9ff9f74eebef1f0322d2eedd402b11060b2b02d4d933a7823edbd7343f47e3a5cd35
6
+ metadata.gz: a89890b81a8d26b13f7ace5e3854b21b503bf7ae3e61c08e4ba0a52257b4644cb29f3c8a03789726aa9e4479b2a0c5f334163536198f8685f9df516539191467
7
+ data.tar.gz: 14402ad63c9918b69789dd0161124a06f74829f400138ff8cf972a36df7ac677b9d62319355b26f6a8da9db38c686d28728431a4d6c81ab3202e7df3c4779df3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (1.22.1)
4
+ avo (1.22.4)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Avo::NavigationLinkComponent < ViewComponent::Base
4
- def initialize(label: nil, path: nil, active: :inclusive, size: :md, target: "_self")
4
+ def initialize(label: nil, path: nil, active: :inclusive, size: :md, target: nil)
5
5
  @label = label
6
6
  @path = path
7
7
  @active = active
@@ -13,6 +13,7 @@ module Avo
13
13
  protect_from_forgery with: :exception
14
14
  before_action :init_app
15
15
  before_action :check_avo_license
16
+ before_action :set_locale
16
17
  before_action :set_authorization
17
18
  before_action :_authenticate!
18
19
  before_action :set_container_classes
@@ -245,5 +246,11 @@ module Avo
245
246
  def model_param_key
246
247
  @resource.form_scope
247
248
  end
249
+
250
+ def set_locale
251
+ I18n.locale = params[:locale] || I18n.default_locale
252
+
253
+ I18n.default_locale = I18n.locale
254
+ end
248
255
  end
249
256
  end
@@ -84,7 +84,6 @@ module Avo
84
84
  def new
85
85
  @model = @resource.model_class.new
86
86
  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
87
- # abort @model.course.inspect
88
87
 
89
88
  @page_title = @resource.default_panel_name
90
89
  add_breadcrumb resource_name.humanize, resources_path(resource: @resource)
@@ -1,4 +1,4 @@
1
- import { Application } from 'stimulus'
1
+ import { Application } from '@hotwired/stimulus'
2
2
 
3
3
  const application = Application.start()
4
4
 
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
  import { castBoolean } from '../helpers/cast_boolean'
3
3
 
4
4
  export default class extends Controller {
@@ -1,5 +1,5 @@
1
1
  import { AttributeObserver } from '@stimulus/mutation-observers'
2
- import { Controller } from 'stimulus'
2
+ import { Controller } from '@hotwired/stimulus'
3
3
 
4
4
  export default class extends Controller {
5
5
  static targets = ['resourceAction', 'standaloneAction']
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  static targets = ['container']
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  static targets = ['form']
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  static targets = ['select', 'type', 'loadAssociationLink'];
@@ -14,7 +14,7 @@ import 'codemirror/mode/vue/vue'
14
14
  import 'codemirror/mode/xml/xml'
15
15
  import 'codemirror/mode/yaml/yaml'
16
16
 
17
- import { Controller } from 'stimulus'
17
+ import { Controller } from '@hotwired/stimulus'
18
18
  import CodeMirror from 'codemirror'
19
19
 
20
20
  import { castBoolean } from '../../helpers/cast_boolean'
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
  import { DateTime } from 'luxon'
3
3
  import flatpickr from 'flatpickr'
4
4
 
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable max-len */
2
- import { Controller } from 'stimulus'
2
+ import { Controller } from '@hotwired/stimulus'
3
3
  import { castBoolean } from '../../helpers/cast_boolean'
4
4
 
5
5
  export default class extends Controller {
@@ -39,7 +39,7 @@ export default class extends Controller {
39
39
 
40
40
  deleteRow(event) {
41
41
  if (this.options.disable_deleting_rows || !this.options.editable) return
42
- const { index } = event.target.dataset
42
+ const { index } = event.params
43
43
  this.fieldValue.splice(index, 1)
44
44
  this.updateTextareaInput()
45
45
  this.updateKeyValueComponent()
@@ -93,7 +93,7 @@ export default class extends Controller {
93
93
  if (this.options.editable) {
94
94
  result += `<a
95
95
  href="javascript:void(0);"
96
- data-index="${index}"
96
+ data-key-value-index-param="${index}"
97
97
  data-action="click->key-value#deleteRow"
98
98
  title="${this.options.delete_text}"
99
99
  data-tippy="tooltip"
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
  import SimpleMDE from 'simplemde'
3
3
 
4
4
  export default class extends Controller {
@@ -1,5 +1,5 @@
1
1
  import 'trix'
2
- import { Controller } from 'stimulus'
2
+ import { Controller } from '@hotwired/stimulus'
3
3
  import { castBoolean } from '../../helpers/cast_boolean'
4
4
 
5
5
  export default class extends Controller {
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
  import URI from 'urijs'
3
3
 
4
4
  export default class extends Controller {
@@ -26,16 +26,12 @@ export default class extends Controller {
26
26
  // then we convert the percent encodings into raw bytes which
27
27
  // can be fed into btoa.
28
28
  return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
29
- function toSolidBytes(match, p1) {
30
- return String.fromCharCode('0x' + p1);
31
- }));
29
+ (match, p1) => String.fromCharCode(`0x${p1}`)))
32
30
  }
33
31
 
34
32
  b64DecodeUnicode(str) {
35
33
  // Going backwards: from bytestream, to percent-encoding, to original string.
36
- return decodeURIComponent(atob(str).split('').map(function(c) {
37
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
38
- }).join(''));
34
+ return decodeURIComponent(atob(str).split('').map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`).join(''))
39
35
  }
40
36
 
41
37
  changeFilter() {
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  static targets = ['content']
@@ -1,20 +1,20 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
- static targets = [ "itemCheckbox", "checkbox" ]
4
+ static targets = ['itemCheckbox', 'checkbox']
5
5
 
6
6
  connect() {
7
7
  this.resourceName = this.element.dataset.resourceName
8
8
  }
9
9
 
10
10
  toggle(event) {
11
- var value = !!event.target.checked
11
+ const value = !!event.target.checked
12
12
  document.querySelectorAll(`[data-controller="item-selector"][data-resource-name="${this.resourceName}"] input[type=checkbox]`)
13
13
  .forEach((checkbox) => checkbox.checked != value && checkbox.click())
14
14
  }
15
15
 
16
16
  update() {
17
- var allSelected = true
17
+ let allSelected = true
18
18
  this.itemCheckboxTargets.forEach((checkbox) => allSelected = allSelected && checkbox.checked)
19
19
  this.checkboxTarget.checked = allSelected
20
20
  }
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  static targets = ['panel'];
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  spinnerMarkup = `<div class="button-spinner">
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  static targets = ['modal']
@@ -8,4 +8,13 @@ export default class extends Controller {
8
8
 
9
9
  document.dispatchEvent(new Event('actions-modal:close'))
10
10
  }
11
+
12
+ delayedClose() {
13
+ const vm = this
14
+
15
+ setTimeout(() => {
16
+ vm.modalTarget.remove()
17
+ document.dispatchEvent(new Event('actions-modal:close'))
18
+ }, 500)
19
+ }
11
20
  }
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
4
  static targets = ['selector']
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
  import * as Mousetrap from 'mousetrap'
3
- import { Controller } from 'stimulus'
3
+ import { Controller } from '@hotwired/stimulus'
4
4
  import { Turbo } from '@hotwired/turbo-rails'
5
5
  import { autocomplete } from '@algolia/autocomplete-js'
6
6
  import URI from 'urijs'
@@ -177,11 +177,16 @@ export default class extends Controller {
177
177
  openOnFocus: true,
178
178
  detachedMediaQuery: '',
179
179
  getSources: ({ query }) => {
180
+ document.body.classList.add('search-loading')
180
181
  const endpoint = that.searchUrl(query)
181
182
 
182
183
  return that
183
184
  .debouncedFetch(endpoint)
184
- .then((response) => response.json())
185
+ .then((response) => {
186
+ document.body.classList.remove('search-loading')
187
+
188
+ return response.json()
189
+ })
185
190
  .then((data) => Object.keys(data).map((resourceName) => that.addSource(resourceName, data[resourceName])))
186
191
  },
187
192
  })
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
  import tippy from 'tippy.js'
3
3
 
4
4
  export default class extends Controller {
@@ -1,4 +1,4 @@
1
- import { Controller } from 'stimulus'
1
+ import { Controller } from '@hotwired/stimulus'
2
2
  import { useClickOutside } from 'stimulus-use'
3
3
 
4
4
  export default class extends Controller {
@@ -7,7 +7,11 @@
7
7
  data-resource-id="<%= params[:id] %>"
8
8
  class="hidden text-slate-800"
9
9
  >
10
- <%= form_with model: @model, scope: 'fields', url: "#{@resource.records_path}/actions/#{@action.param_id}", data: {'turbo-frame': '_top', 'action-target': 'form'} do |form| %>
10
+ <%= form_with model: @model,
11
+ scope: 'fields',
12
+ url: "#{@resource.records_path}/actions/#{@action.param_id}",
13
+ data: @action.class.form_data_attributes do |form|
14
+ %>
11
15
  <%= render Avo::ModalComponent.new do |c| %>
12
16
  <% c.heading do %>
13
17
  <%= @action.action_name %>
@@ -26,8 +30,8 @@
26
30
  <% end %>
27
31
 
28
32
  <% c.controls do %>
29
- <%= a_button @action.cancel_button_label, 'data-action': 'click->modal#close', size: :sm %>
30
- <%= a_button @action.confirm_button_label, type: :submit, color: :green, size: :sm %>
33
+ <%= a_button @action.cancel_button_label, data: { action: 'click->modal#close' }, size: :sm %>
34
+ <%= a_button @action.confirm_button_label, type: :submit, color: :green, size: :sm, data: @action.class.submit_button_data_attributes %>
31
35
  <% end %>
32
36
  <% end %>
33
37
  <% end %>
data/db/factories.rb CHANGED
@@ -42,11 +42,11 @@ FactoryBot.define do
42
42
  end
43
43
 
44
44
  factory :comment do
45
- body { Faker::Lorem.paragraphs(number: rand(4...10)) }
45
+ body { Faker::Lorem.paragraphs(number: rand(4...10)).join(' ') }
46
46
  end
47
47
 
48
48
  factory :review do
49
- body { Faker::Lorem.paragraphs(number: rand(4...10)) }
49
+ body { Faker::Lorem.paragraphs(number: rand(4...10)).join(' ') }
50
50
  end
51
51
 
52
52
  factory :person do
@@ -15,6 +15,7 @@ module Avo
15
15
  class_attribute :fields
16
16
  class_attribute :standalone, default: false
17
17
  class_attribute :visible
18
+ class_attribute :may_download_file, default: false
18
19
 
19
20
  attr_accessor :response
20
21
  attr_accessor :model
@@ -22,6 +23,26 @@ module Avo
22
23
  attr_accessor :user
23
24
  attr_accessor :fields_loader
24
25
 
26
+ class << self
27
+ def form_data_attributes
28
+ # We can't respond with a file download from Turbo se we disable it on the form
29
+ if may_download_file
30
+ { 'turbo': false }
31
+ else
32
+ { 'turbo-frame': '_top', 'action-target': 'form' }
33
+ end
34
+ end
35
+
36
+ # We can't respond with a file download from Turbo se we disable close the modal manually after a while (it's a hack, we know)
37
+ def submit_button_data_attributes
38
+ if may_download_file
39
+ { action: 'click->modal#delayedClose' }
40
+ else
41
+ {}
42
+ end
43
+ end
44
+ end
45
+
25
46
  def action_name
26
47
  return name if name.present?
27
48
 
@@ -127,23 +127,29 @@ module Avo
127
127
  field.visible?
128
128
  end
129
129
  .select do |field|
130
+ is_valid = true
131
+
130
132
  # Strip out the reflection field in index queries with a parent association.
131
133
  if reflection.present?
132
- if reflection.options.present? &&
133
- field.respond_to?(:polymorphic_as) &&
134
- field.polymorphic_as.to_s == reflection.options[:as].to_s
135
- next
136
- end
137
-
138
- if field.respond_to?(:foreign_key) &&
139
- reflection.respond_to?(:foreign_key) &&
140
- reflection.foreign_key != field.foreign_key &&
141
- @params[:resource_name] == field.resource.model_key
142
- next
134
+ # regular non-polymorphic association
135
+ # we're matching the reflection inverse_of foriegn key with the field's foreign_key
136
+ if field.is_a?(Avo::Fields::BelongsToField)
137
+ if field.respond_to?(:foreign_key) &&
138
+ reflection.inverse_of.foreign_key == field.foreign_key
139
+ is_valid = false
140
+ end
141
+
142
+ # polymorphic association
143
+ if field.respond_to?(:foreign_key) &&
144
+ field.is_polymorphic? &&
145
+ reflection.respond_to?(:polymorphic?) &&
146
+ reflection.inverse_of.foreign_key == field.reflection.foreign_key
147
+ is_valid = false
148
+ end
143
149
  end
144
150
  end
145
151
 
146
- true
152
+ is_valid
147
153
  end
148
154
 
149
155
  if panel.present?
@@ -136,6 +136,8 @@ module Avo
136
136
 
137
137
  def is_polymorphic?
138
138
  polymorphic_as.present?
139
+ rescue
140
+ false
139
141
  end
140
142
 
141
143
  def foreign_key
@@ -154,6 +156,13 @@ module Avo
154
156
  nil
155
157
  end
156
158
 
159
+ # Get the model reflection instance
160
+ def reflection
161
+ reflection_for_key(id)
162
+ rescue
163
+ nil
164
+ end
165
+
157
166
  def relation_model_class
158
167
  @resource.model_class
159
168
  end
@@ -23,6 +23,12 @@ module Avo
23
23
 
24
24
  begin
25
25
  perform_and_cache_request
26
+ rescue Errno::EHOSTUNREACH => exception
27
+ cache_and_return_error "HTTP host not reachable error.", exception.message
28
+ rescue Errno::ECONNRESET => exception
29
+ cache_and_return_error "HTTP connection reset error.", exception.message
30
+ rescue Errno::ECONNREFUSED => exception
31
+ cache_and_return_error "HTTP connection refused error.", exception.message
26
32
  rescue HTTParty::Error => exception
27
33
  cache_and_return_error "HTTP client error.", exception.message
28
34
  rescue Net::OpenTimeout => exception
@@ -71,7 +77,55 @@ module Avo
71
77
  environment: Rails.env,
72
78
  ip: current_request.ip,
73
79
  host: current_request.host,
74
- port: current_request.port
80
+ port: current_request.port,
81
+ app_name: app_name
82
+ }
83
+ end
84
+
85
+ def app_name
86
+ Rails.application.class.to_s.split("::").first
87
+ rescue
88
+ nil
89
+ end
90
+
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
+ def other_metadata(type = :actions)
120
+ resources = App.resources
121
+
122
+ types = resources.map(&:"get_#{type}")
123
+ type_count = types.flatten.uniq.count
124
+ type_per_resource = sprintf("%0.01f", types.map(&:count).sum / (resources.count + 0.0))
125
+
126
+ {
127
+ "#{type}_count": type_count,
128
+ "#{type}_per_resource": type_per_resource,
75
129
  }
76
130
  end
77
131
 
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "1.22.1"
2
+ VERSION = "1.22.4"
3
3
  end