diff --git a/app/components/avo/alert_component.html.erb b/app/components/avo/alert_component.html.erb index c712c13f65..6c945ad217 100644 --- a/app/components/avo/alert_component.html.erb +++ b/app/components/avo/alert_component.html.erb @@ -2,7 +2,7 @@ data-alert-dismiss-after-value="<%= Avo.configuration.alert_dismiss_time %>" data-alert-remove-delay-value="0" class="<%= classes %>" - > +>
diff --git a/app/components/avo/alert_component.rb b/app/components/avo/alert_component.rb index 74e0ed21f0..064da16068 100644 --- a/app/components/avo/alert_component.rb +++ b/app/components/avo/alert_component.rb @@ -12,7 +12,7 @@ def initialize(type, message) end def icon - return "heroicons/solid/x-circle" if is_error? + return "heroicons/solid/exclamation-circle" if is_error? return "heroicons/solid/exclamation" if is_warning? return "heroicons/solid/exclamation-circle" if is_info? return "heroicons/solid/check-circle" if is_success? diff --git a/app/components/avo/backtrace_alert_component.html.erb b/app/components/avo/backtrace_alert_component.html.erb new file mode 100644 index 0000000000..debaf26be7 --- /dev/null +++ b/app/components/avo/backtrace_alert_component.html.erb @@ -0,0 +1,26 @@ +
+
+
+
+ <%= helpers.svg "heroicons/solid/exclamation-circle", class: "h-6" %> +
+
+

+ Backtrace: +

<%= @backtrace.join("\n") %>
+

+
+
+ +
+
+
+
diff --git a/app/components/avo/backtrace_alert_component.rb b/app/components/avo/backtrace_alert_component.rb new file mode 100644 index 0000000000..f190e98b49 --- /dev/null +++ b/app/components/avo/backtrace_alert_component.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Avo::BacktraceAlertComponent < ViewComponent::Base + include Avo::ApplicationHelper + + def initialize(backtrace: nil) + @backtrace = backtrace + end + + def render? + @backtrace.present? + end +end diff --git a/app/controllers/avo/base_controller.rb b/app/controllers/avo/base_controller.rb index e675fe8bc6..8b7da8fea2 100644 --- a/app/controllers/avo/base_controller.rb +++ b/app/controllers/avo/base_controller.rb @@ -219,32 +219,18 @@ def destroy_record_action def perform_action_and_record_errors(&block) begin succeeded = block.call + rescue ActiveRecord::RecordInvalid => e + # Do nothing as the record errors are already being displayed rescue => exception # In case there's an error somewhere else than the record # Example: When you save a license that should create a user for it and creating that user throws and error. # Example: When you Try to delete a record and has a foreign key constraint. - exception_message = exception.message + @record.errors.add(:base, exception.message) + @backtrace = exception.backtrace end - # Add the errors from the record - @errors = @record.errors.full_messages - - # Remove duplicated errors - if exception_message.present? - @errors = @errors.reject { |error| exception_message.include? error } - end - - # Figure out if we have to output the exception_message - # Usually it means that it's not a validation error but something else - if exception_message.present? - exception_is_validation = @errors.select { |error| exception_message.include? error }.present? - end - - if exception_is_validation || (@errors.blank? && exception_message.present?) - @errors << exception_message - end - - @errors.any? ? false : succeeded + # This method only needs to return true or false to indicate if the action was successful + @record.errors.any? ? false : succeeded end def model_params @@ -533,7 +519,8 @@ def destroy_success_message end def destroy_fail_message - @errors.present? ? @errors.join(". ") : t("avo.failed") + errors = @record.errors.full_messages + errors.present? ? errors.join(". ") : t("avo.failed") end def after_destroy_path diff --git a/app/views/avo/base/create_fail_action.turbo_stream.erb b/app/views/avo/base/create_fail_action.turbo_stream.erb index 735b172e29..bdc0dbb3a9 100644 --- a/app/views/avo/base/create_fail_action.turbo_stream.erb +++ b/app/views/avo/base/create_fail_action.turbo_stream.erb @@ -1,21 +1,5 @@ <%= turbo_stream.replace(frame_id(@resource), template: "avo/base/new") %> <%= turbo_stream.append "alerts" do %> - <%= render Avo::FlashAlertsComponent.new flashes: flash %> -<% end %> - -<% if @errors.any? %> - <%= turbo_stream.append("alerts") do %> - <% @errors.each do |message| %> - <%= render Avo::AlertComponent.new :error, message %> - <% end %> - <% end %> -<% end %> - -<% if @record.errors.any? %> - <%= turbo_stream.append("alerts") do %> - <% @record.errors.full_messages.each do |message| %> - <%= render Avo::AlertComponent.new :error, message %> - <% end %> - <% end %> + <%= render partial: "avo/partials/all_alerts" %> <% end %> diff --git a/app/views/avo/base/update_fail_action.turbo_stream.erb b/app/views/avo/base/update_fail_action.turbo_stream.erb index 034047be81..40f6b6bb1a 100644 --- a/app/views/avo/base/update_fail_action.turbo_stream.erb +++ b/app/views/avo/base/update_fail_action.turbo_stream.erb @@ -1,21 +1,5 @@ <%= turbo_stream.replace(frame_id(@resource), template: "avo/base/edit") %> <%= turbo_stream.append "alerts" do %> - <%= render Avo::FlashAlertsComponent.new flashes: flash %> -<% end %> - -<% if @errors.any? %> - <%= turbo_stream.append("alerts") do %> - <% @errors.each do |message| %> - <%= render Avo::AlertComponent.new :error, message %> - <% end %> - <% end %> -<% end %> - -<% if @record.errors.any? %> - <%= turbo_stream.append("alerts") do %> - <% @record.errors.full_messages.each do |message| %> - <%= render Avo::AlertComponent.new :error, message %> - <% end %> - <% end %> + <%= render partial: "avo/partials/all_alerts" %> <% end %> diff --git a/app/views/avo/partials/_alerts.html.erb b/app/views/avo/partials/_alerts.html.erb index 8be38d1c28..d562c4e10d 100644 --- a/app/views/avo/partials/_alerts.html.erb +++ b/app/views/avo/partials/_alerts.html.erb @@ -1,9 +1,3 @@ <%= 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 %> - <%= render Avo::FlashAlertsComponent.new flashes: flash %> - <% # In case we have other general error messages %> - <% if @errors.present? %> - <% @errors.each do |message| %> - <%= render Avo::AlertComponent.new :error, message %> - <% end %> - <% end %> + <%= render partial: "avo/partials/all_alerts" %> <% end %> diff --git a/app/views/avo/partials/_all_alerts.html.erb b/app/views/avo/partials/_all_alerts.html.erb new file mode 100644 index 0000000000..fb3abe9ca6 --- /dev/null +++ b/app/views/avo/partials/_all_alerts.html.erb @@ -0,0 +1,11 @@ +<%= render Avo::FlashAlertsComponent.new flashes: flash %> + +<% if @record&.errors&.any? %> + <% @record.errors.full_messages.each do |message| %> + <%= render Avo::AlertComponent.new :error, message %> + <% end %> +<% end %> + +<% if @backtrace&.present? && Avo::Current.user_is_developer? %> + <%= render Avo::BacktraceAlertComponent.new backtrace: @backtrace %> +<% end %> diff --git a/lib/avo/configuration.rb b/lib/avo/configuration.rb index 7c81a1d7c6..416a67cfce 100644 --- a/lib/avo/configuration.rb +++ b/lib/avo/configuration.rb @@ -51,6 +51,8 @@ class Configuration attr_accessor :default_url_options attr_accessor :click_row_to_view_record attr_accessor :alert_dismiss_time + attr_accessor :is_admin_method + attr_accessor :is_developer_method attr_accessor :search_results_count def initialize @@ -110,6 +112,8 @@ def initialize @pagination = {} @click_row_to_view_record = false @alert_dismiss_time = 5000 + @is_admin_method = :is_admin? + @is_developer_method = :is_developer? @search_results_count = 8 end diff --git a/lib/avo/current.rb b/lib/avo/current.rb index 4ecd87b56f..9baf2da9ac 100644 --- a/lib/avo/current.rb +++ b/lib/avo/current.rb @@ -24,4 +24,16 @@ def params def request view_context&.request || ActionDispatch::Request.empty end + + def user_is_admin? + return false unless user&.respond_to?(Avo.configuration.is_admin_method) + + user.send(Avo.configuration.is_admin_method) + end + + def user_is_developer? + return false unless user&.respond_to?(Avo.configuration.is_developer_method) + + user.send(Avo.configuration.is_developer_method) + end end diff --git a/lib/generators/avo/templates/initializer/avo.tt b/lib/generators/avo/templates/initializer/avo.tt index afb7f38840..6ede49e65b 100644 --- a/lib/generators/avo/templates/initializer/avo.tt +++ b/lib/generators/avo/templates/initializer/avo.tt @@ -23,6 +23,8 @@ Avo.configure do |config| ## == Authentication == # config.current_user_method = {} + # config.is_admin_method = :is_admin + # config.is_developer_method = :is_developer # config.authenticate_with do # end diff --git a/spec/dummy/app/avo/resources/post.rb b/spec/dummy/app/avo/resources/post.rb index c1cdc62ef4..b74a70e5ae 100644 --- a/spec/dummy/app/avo/resources/post.rb +++ b/spec/dummy/app/avo/resources/post.rb @@ -70,7 +70,7 @@ def fields { cover_url: if record.cover_photo.attached? - main_app.url_for(record.cover_photo.url) + main_app.url_for(record.cover_photo) end, title: record.name, body: helpers.extract_excerpt(record.body) diff --git a/spec/dummy/app/models/course.rb b/spec/dummy/app/models/course.rb index fff5aed3e8..fb1a6bf687 100644 --- a/spec/dummy/app/models/course.rb +++ b/spec/dummy/app/models/course.rb @@ -20,6 +20,12 @@ class Course < ApplicationRecord validates :name, presence: true + # Used to test the backtrace alert + after_save do + raise "raised" if ENV["TEST_BACKTRACE_ALERT"] == "1" + # raise "raised" + end + def has_skills true end diff --git a/spec/dummy/app/models/user.rb b/spec/dummy/app/models/user.rb index ac3b9a2ff2..ed46e707cf 100644 --- a/spec/dummy/app/models/user.rb +++ b/spec/dummy/app/models/user.rb @@ -84,4 +84,8 @@ def self.ransackable_attributes(auth_object = nil) def accounts [OpenStruct.new(id: 1, name: "Foo"), OpenStruct.new(id: 2, name: "Bar")] end + + def is_developer? + true + end end diff --git a/spec/dummy/config/initializers/avo.rb b/spec/dummy/config/initializers/avo.rb index 15e214f8dc..b5145a45c6 100644 --- a/spec/dummy/config/initializers/avo.rb +++ b/spec/dummy/config/initializers/avo.rb @@ -17,6 +17,8 @@ ## == App context == config.current_user_method = :current_user + # config.is_admin_method = :is_admin? + # config.is_developer_method = :is_developer? config.model_resource_mapping = { User: "User" } diff --git a/spec/system/avo/alert_backtrace_spec.rb b/spec/system/avo/alert_backtrace_spec.rb new file mode 100644 index 0000000000..cb143112ff --- /dev/null +++ b/spec/system/avo/alert_backtrace_spec.rb @@ -0,0 +1,19 @@ +require "rails_helper" + +RSpec.describe "Alert Backtrace", type: :system do + before do + ENV["TEST_BACKTRACE_ALERT"] = "1" + end + + it "responds with a backtrace alert" do + visit "/admin/resources/courses/new" + + fill_in "Name", with: "Test" + + save + + expect(page).to have_text "raised" + expect(page).to have_text "Backtrace:" + expect(page).to have_text "/dummy/app/models/course.rb:25:in `block in " + end +end