avo 1.22.2 → 1.23.0

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

Potentially problematic release.


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

Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +6 -0
  4. data/app/{views/avo/partials/_alert.html.erb → components/avo/alert_component.html.erb} +0 -0
  5. data/app/components/avo/alert_component.rb +11 -0
  6. data/app/components/avo/alerts_component.html.erb +3 -0
  7. data/app/components/avo/alerts_component.rb +5 -0
  8. data/app/components/avo/navigation_link_component.rb +1 -1
  9. data/app/{views/avo/partials/_turbo_frame_wrap.html.erb → components/avo/turbo_frame_wrapper_component.html.erb} +2 -2
  10. data/app/components/avo/turbo_frame_wrapper_component.rb +9 -0
  11. data/app/controllers/avo/application_controller.rb +7 -0
  12. data/app/controllers/avo/base_controller.rb +0 -1
  13. data/app/helpers/avo/application_helper.rb +1 -1
  14. data/app/javascript/js/application.js +1 -1
  15. data/app/javascript/js/controllers/action_controller.js +1 -1
  16. data/app/javascript/js/controllers/actions_picker_controller.js +1 -1
  17. data/app/javascript/js/controllers/alerts_controller.js +1 -1
  18. data/app/javascript/js/controllers/attachments_controller.js +1 -1
  19. data/app/javascript/js/controllers/fields/belongs_to_field_controller.js +1 -1
  20. data/app/javascript/js/controllers/fields/code_field_controller.js +1 -1
  21. data/app/javascript/js/controllers/fields/date_field_controller.js +1 -1
  22. data/app/javascript/js/controllers/fields/key_value_controller.js +3 -3
  23. data/app/javascript/js/controllers/fields/simple_mde_controller.js +1 -1
  24. data/app/javascript/js/controllers/fields/trix_field_controller.js +1 -1
  25. data/app/javascript/js/controllers/filter_controller.js +3 -7
  26. data/app/javascript/js/controllers/hidden_input_controller.js +1 -1
  27. data/app/javascript/js/controllers/item_select_all_controller.js +4 -4
  28. data/app/javascript/js/controllers/item_selector_controller.js +1 -1
  29. data/app/javascript/js/controllers/loading_button_controller.js +1 -1
  30. data/app/javascript/js/controllers/modal_controller.js +10 -1
  31. data/app/javascript/js/controllers/per_page_controller.js +1 -1
  32. data/app/javascript/js/controllers/search_controller.js +7 -2
  33. data/app/javascript/js/controllers/tippy_controller.js +1 -1
  34. data/app/javascript/js/controllers/toggle_panel_controller.js +1 -1
  35. data/app/views/avo/actions/show.html.erb +7 -3
  36. data/app/views/layouts/avo/application.html.erb +1 -1
  37. data/lib/avo/base_action.rb +21 -0
  38. data/lib/avo/licensing/h_q.rb +55 -1
  39. data/lib/avo/version.rb +1 -1
  40. data/public/avo-assets/avo.js +2569 -1926
  41. data/public/avo-assets/avo.js.map +3 -3
  42. metadata +8 -5
  43. data/app/views/avo/partials/_alerts.html.erb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1b3e02ee566af1fcf8ac642f0b59693fc9ca60840f5a57e46c0e57df32831c9
4
- data.tar.gz: 621dc29f10b4693db5e09531fb7cd8cd591666e0be8d098d3927a48e0e2cb0dd
3
+ metadata.gz: 93ddc5b52a7eb149886833f2eda8dcf69e5cfa29af5d357f57dfbc3b6e03800e
4
+ data.tar.gz: 44050a09443ff49550f7d9e3c22c3a4b03eea83f9c0d3a08bbc4a97d5084a355
5
5
  SHA512:
6
- metadata.gz: 325009723e404b72ab8ff8cde8c0491ecf4c520b075b7731ee837c56998f1c45d57b50805ebbce1358dfff3b9f7e6b8a53d3ada14a9fe365523a6a3f077dbf29
7
- data.tar.gz: de4a2bcb927f0d1eb4053185e3683c0d44d06c0fce7848f3740edaee52cdabfc3c6a5cde067b6ad9f8506013ea8eb214639073e19793cc9fc4e51b72bbdcaf38
6
+ metadata.gz: 346e58ffb98b4b60576282f43a766f6e9e388dcf83041dffc8b1f9c1e81292271869e2f356df92471afa77e51eff5945f127a236e2adc1c41a24ccb6847d2a5f
7
+ data.tar.gz: 5dbeae53b1d1179117f035c1bc2b806c9f88064f5af9c9c1498fbd6fe24107d96a67c02345f437f2cd73789de4935932c97f52730142e970808a68e49c5e2aa0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (1.22.2)
4
+ avo (1.23.0)
5
5
  active_link_to
6
6
  addressable
7
7
  breadcrumbs_on_rails
data/README.md CHANGED
@@ -73,3 +73,9 @@ Please read the [UPGRADE_GUIDE.MD](https://docs.avohq.io/1.0/upgrade.html)
73
73
 
74
74
 
75
75
  ![Alt](https://repobeats.axiom.co/api/embed/1481a6a259064f02a7936470d12a50802a9c98a4.svg "Repobeats analytics image")
76
+
77
+ # Shoutouts
78
+
79
+ [Get a box of waffles and some of the best app monitoring from Appsignal](https://appsignal.com/r/93dbe69bfb) 🧇
80
+
81
+ [Get $100 in credits from Digital Ocean](https://www.digitalocean.com/?refcode=efc1fe881d74&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge) 💸
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::AlertComponent < ViewComponent::Base
4
+ attr_reader :type
5
+ attr_reader :message
6
+
7
+ def initialize(type, message)
8
+ @type = type
9
+ @message = message
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ <% helpers.flash.each do |type, message| %>
2
+ <%= render Avo::AlertComponent.new type.to_sym, message %>
3
+ <% end %>
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::AlertsComponent < ViewComponent::Base
4
+
5
+ end
@@ -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
@@ -3,7 +3,7 @@
3
3
  # When rendering the frames the flashed content gets lost.
4
4
  # By including the alerts partial, the stimulus will pick them up and display them to the user.
5
5
  %>
6
- <%= render partial: 'avo/partials/alerts' if flash.present? && name.present? %>
6
+ <%= render Avo::AlertsComponent.new if helpers.flash.present? && name.present? %>
7
7
 
8
- <%= yield %>
8
+ <%= content %>
9
9
  <% if name.present? %></turbo-frame><% end %>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Avo::TurboFrameWrapperComponent < ViewComponent::Base
4
+ attr_reader :name
5
+
6
+ def initialize(name = nil)
7
+ @name = name
8
+ end
9
+ end
@@ -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)
@@ -21,7 +21,7 @@ module Avo
21
21
  end
22
22
 
23
23
  def turbo_frame_wrap(name, &block)
24
- render layout: "avo/partials/turbo_frame_wrap", locals: {name: name} do
24
+ render Avo::TurboFrameWrapperComponent.new name do
25
25
  capture(&block)
26
26
  end
27
27
  end
@@ -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 %>
@@ -51,7 +51,7 @@
51
51
  <%= turbo_frame_tag 'destroy_attachment_form' %>
52
52
 
53
53
  <%= turbo_frame_tag 'alerts' do %>
54
- <%= render partial: 'avo/partials/alerts' %>
54
+ <%= render Avo::AlertsComponent.new %>
55
55
  <% end %>
56
56
  </div>
57
57
 
@@ -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
 
@@ -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.2"
2
+ VERSION = "1.23.0"
3
3
  end