diff --git a/.gitignore b/.gitignore index d6cd92d3..d6620459 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /test/dummy/storage/ /test/dummy/tmp/ .DS_Store +.ruby-gemset diff --git a/Gemfile.lock b/Gemfile.lock index c06c7190..185a970a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -28,10 +28,9 @@ GIT PATH remote: . specs: - mission_control-jobs (0.3.1) + mission_control-jobs (0.3.3) importmap-rails irb (~> 1.13) - propshaft rails (>= 7.1) stimulus-rails turbo-rails @@ -191,11 +190,6 @@ GEM parser (3.3.0.5) ast (~> 2.4.1) racc - propshaft (0.8.0) - actionpack (>= 7.0.0) - activesupport (>= 7.0.0) - rack - railties (>= 7.0.0) psych (5.1.2) stringio public_suffix (5.0.4) diff --git a/README.md b/README.md index 5c740e26..cc3d4332 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Besides `base_controller_class`, you can also set the following for `MissionCont - `internal_query_count_limit`: in count queries, the maximum number of records that will be counted if the adapter needs to limit these queries. True counts above this number will be returned as `INFINITY`. This keeps count queries fast—defaults to `500,000` - `scheduled_job_delay_threshold`: the time duration before a scheduled job is considered delayed. Defaults to `1.minute` (a job is considered delayed if it hasn't transitioned from the `scheduled` status 1 minute after the scheduled time). - `show_console_help`: whether to show the console help. If you don't want the console help message, set this to `false`—defaults to `true`. +- `backtrace_cleaner`: a backtrace cleaner used for optionally filtering backtraces on the Failed Jobs detail page. Defaults to `Rails::BacktraceCleaner.new`. See the [Advanced configuration](#advanced-configuration) section for how to configure/override this setting on a per application/server basis. This library extends Active Job with a querying interface and the following setting: - `config.active_job.default_page_size`: the internal batch size that Active Job will use when sending queries to the underlying adapter and the batch size for the bulk operations defined above—defaults to `1000`. @@ -107,7 +108,24 @@ SERVERS_BY_APP.each do |app, servers| ActiveJob::QueueAdapters::SolidQueueAdapter.new end - [ server, queue_adapter ] + # Default: + # + # @return Array> + # * This is equivalent, and behaves identically to, the format the default format above. + # [ server, [ queue_adapter ]] # without optional backtrace cleaner + # + # @return Array> + # * This format adds an optional ActiveSupport::BacktraceCleaner to override the system wide + # backtrace cleaner for *this* Application Server/Service. + # [ server, [ queue_adapter, BacktraceCleaner.new ]] # with optional backtrace cleaner end.to_h MissionControl::Jobs.applications.add(app, queue_adapters_by_name) diff --git a/app/assets/config/mission_control_jobs_manifest.js b/app/assets/config/mission_control_jobs_manifest.js new file mode 100644 index 00000000..7ca72390 --- /dev/null +++ b/app/assets/config/mission_control_jobs_manifest.js @@ -0,0 +1,4 @@ +//= link_directory ../stylesheets/mission_control/jobs .css +//= link_directory ../../javascript/mission_control/jobs .js +//= link_directory ../../javascript/mission_control/jobs/controllers .js +//= link_directory ../../javascript/mission_control/jobs/helpers .js diff --git a/app/assets/stylesheets/mission_control/jobs/application.css b/app/assets/stylesheets/mission_control/jobs/application.css index 0f783bf7..3951e525 100644 --- a/app/assets/stylesheets/mission_control/jobs/application.css +++ b/app/assets/stylesheets/mission_control/jobs/application.css @@ -1,2 +1,16 @@ -@import url("./forms.css"); -@import url("./jobs.css"); +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ + diff --git a/app/controllers/mission_control/jobs/application_controller.rb b/app/controllers/mission_control/jobs/application_controller.rb index 1413bacb..d9a81faf 100644 --- a/app/controllers/mission_control/jobs/application_controller.rb +++ b/app/controllers/mission_control/jobs/application_controller.rb @@ -1,14 +1,6 @@ class MissionControl::Jobs::ApplicationController < MissionControl::Jobs.base_controller_class.constantize - ActionController::Base::MODULES.each do |mod| - include mod unless self < mod - end - layout "mission_control/jobs/application" - # Include helpers if not already included - helper MissionControl::Jobs::ApplicationHelper unless self < MissionControl::Jobs::ApplicationHelper - helper Importmap::ImportmapTagsHelper unless self < Importmap::ImportmapTagsHelper - include MissionControl::Jobs::ApplicationScoped, MissionControl::Jobs::NotFoundRedirections include MissionControl::Jobs::AdapterFeatures diff --git a/app/controllers/mission_control/jobs/jobs_controller.rb b/app/controllers/mission_control/jobs/jobs_controller.rb index 8882fe8f..c9d8e089 100644 --- a/app/controllers/mission_control/jobs/jobs_controller.rb +++ b/app/controllers/mission_control/jobs/jobs_controller.rb @@ -15,6 +15,7 @@ def show end private + def jobs_relation filtered_jobs end diff --git a/app/helpers/mission_control/jobs/jobs_helper.rb b/app/helpers/mission_control/jobs/jobs_helper.rb index 3fc80c01..14219e6c 100644 --- a/app/helpers/mission_control/jobs/jobs_helper.rb +++ b/app/helpers/mission_control/jobs/jobs_helper.rb @@ -11,8 +11,16 @@ def failed_job_error(job) "#{job.last_execution_error.error_class}: #{job.last_execution_error.message}" end - def failed_job_backtrace(job) - job.last_execution_error.backtrace.join("\n") + def clean_backtrace? + params["clean_backtrace"] == "true" + end + + def failed_job_backtrace(job, server) + if clean_backtrace? && server&.backtrace_cleaner + server.backtrace_cleaner.clean(job.last_execution_error.backtrace).join("\n") + else + job.last_execution_error.backtrace.join("\n") + end end def attribute_names_for_job_status(status) @@ -31,6 +39,7 @@ def job_delayed?(job) end private + def renderable_job_arguments_for(job) job.serialized_arguments.collect do |argument| as_renderable_argument(argument) diff --git a/app/views/layouts/mission_control/jobs/application.html.erb b/app/views/layouts/mission_control/jobs/application.html.erb index 38d3da66..64c8dc5c 100644 --- a/app/views/layouts/mission_control/jobs/application.html.erb +++ b/app/views/layouts/mission_control/jobs/application.html.erb @@ -7,7 +7,7 @@ - + <%= stylesheet_link_tag "mission_control/jobs/application", "data-turbo-track": "reload" %> <%= javascript_importmap_tags "application", importmap: MissionControl::Jobs.importmap %> diff --git a/app/views/mission_control/jobs/jobs/_error_information.html.erb b/app/views/mission_control/jobs/jobs/_error_information.html.erb index bd52a895..0f61a0c6 100644 --- a/app/views/mission_control/jobs/jobs/_error_information.html.erb +++ b/app/views/mission_control/jobs/jobs/_error_information.html.erb @@ -14,5 +14,9 @@ -
<%= failed_job_backtrace(job) %>
+ <% if @server.backtrace_cleaner %> + <%= render "mission_control/jobs/jobs/failed/backtrace_toggle", application: @application, job: job %> + <% end %> + +
<%= failed_job_backtrace(job, @server) %>
<% end %> diff --git a/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb b/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb new file mode 100644 index 00000000..8f137990 --- /dev/null +++ b/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb @@ -0,0 +1,14 @@ +<%# locals: (application:, job:) %> + +
+
+
    +
  • + <%= link_to "Clean", application_job_path(application, job.job_id, clean_backtrace: true) %> +
  • +
  • + <%= link_to "Full", application_job_path(application, job.job_id, clean_backtrace: false) %> +
  • +
+
+
\ No newline at end of file diff --git a/lib/mission_control/jobs.rb b/lib/mission_control/jobs.rb index 0bd019a8..366c2a5e 100644 --- a/lib/mission_control/jobs.rb +++ b/lib/mission_control/jobs.rb @@ -20,5 +20,6 @@ module Jobs mattr_accessor :show_console_help, default: true mattr_accessor :scheduled_job_delay_threshold, default: 1.minute mattr_accessor :importmap, default: Importmap::Map.new + mattr_accessor :backtrace_cleaner end end diff --git a/lib/mission_control/jobs/application.rb b/lib/mission_control/jobs/application.rb index c68eb54d..bd04c81a 100644 --- a/lib/mission_control/jobs/application.rb +++ b/lib/mission_control/jobs/application.rb @@ -11,7 +11,10 @@ def initialize(name:) def add_servers(queue_adapters_by_name) queue_adapters_by_name.each do |name, queue_adapter| - servers << MissionControl::Jobs::Server.new(name: name.to_s, queue_adapter: queue_adapter, application: self) + adapter, cleaner = queue_adapter + + servers << MissionControl::Jobs::Server.new(name: name.to_s, queue_adapter: adapter, + backtrace_cleaner: cleaner, application: self) end end end diff --git a/lib/mission_control/jobs/engine.rb b/lib/mission_control/jobs/engine.rb index d1d2d6ed..876db3c2 100644 --- a/lib/mission_control/jobs/engine.rb +++ b/lib/mission_control/jobs/engine.rb @@ -4,20 +4,18 @@ require "importmap-rails" require "turbo-rails" require "stimulus-rails" -require "propshaft" module MissionControl module Jobs class Engine < ::Rails::Engine isolate_namespace MissionControl::Jobs - config.middleware.use ActionDispatch::Flash unless config.action_dispatch.flash - config.mission_control = ActiveSupport::OrderedOptions.new unless config.try(:mission_control) config.mission_control.jobs = ActiveSupport::OrderedOptions.new config.before_initialize do config.mission_control.jobs.applications = MissionControl::Jobs::Applications.new + config.mission_control.jobs.backtrace_cleaner ||= Rails::BacktraceCleaner.new config.mission_control.jobs.each do |key, value| MissionControl::Jobs.public_send("#{key}=", value) @@ -90,7 +88,6 @@ class Engine < ::Rails::Engine end initializer "mission_control-jobs.assets" do |app| - app.config.assets.paths << root.join("app/assets/stylesheets") app.config.assets.paths << root.join("app/javascript") app.config.assets.precompile += %w[ mission_control_jobs_manifest ] end diff --git a/lib/mission_control/jobs/server.rb b/lib/mission_control/jobs/server.rb index e5877b46..970d5f5a 100644 --- a/lib/mission_control/jobs/server.rb +++ b/lib/mission_control/jobs/server.rb @@ -4,12 +4,13 @@ class MissionControl::Jobs::Server include MissionControl::Jobs::IdentifiedByName include Serializable, RecurringTasks, Workers - attr_reader :name, :queue_adapter, :application + attr_reader :name, :queue_adapter, :application, :backtrace_cleaner - def initialize(name:, queue_adapter:, application:) + def initialize(name:, queue_adapter:, application:, backtrace_cleaner: nil) super(name: name) @queue_adapter = queue_adapter @application = application + @backtrace_cleaner = backtrace_cleaner || MissionControl::Jobs.backtrace_cleaner end def activating(&block) diff --git a/lib/mission_control/jobs/version.rb b/lib/mission_control/jobs/version.rb index bdbcf43a..878554e2 100644 --- a/lib/mission_control/jobs/version.rb +++ b/lib/mission_control/jobs/version.rb @@ -1,5 +1,5 @@ module MissionControl module Jobs - VERSION = "0.3.1" + VERSION = "0.3.3" end end diff --git a/mission_control-jobs.gemspec b/mission_control-jobs.gemspec index 9b886316..fc03152c 100644 --- a/mission_control-jobs.gemspec +++ b/mission_control-jobs.gemspec @@ -17,7 +17,6 @@ Gem::Specification.new do |spec| end spec.add_dependency "rails", ">= 7.1" - spec.add_dependency "propshaft" spec.add_dependency "importmap-rails" spec.add_dependency "turbo-rails" spec.add_dependency "stimulus-rails" diff --git a/test/system/show_failed_job_test.rb b/test/system/show_failed_job_test.rb index df12e81d..567c0b8c 100644 --- a/test/system/show_failed_job_test.rb +++ b/test/system/show_failed_job_test.rb @@ -29,4 +29,68 @@ class ShowFailedJobsTest < ApplicationSystemTestCase visit jobs_path(:failed) assert_text /there are no failed jobs/i end + + test "Has Clean/Full buttons when a backtrace cleaner is configured" do + visit jobs_path(:failed) + within_job_row(/FailingJob\s*2/) do + click_on "RuntimeError: This always fails!" + end + + assert_selector ".backtrace-toggle-selector" + end + + test "Does not offer Clean/Full buttons when a backtrace cleaner is not configured" do + setup do + # grab the current state + @backtrace_cleaner = MissionControl::Jobs.backtrace_cleaner + @applications = MissionControl::Jobs.backtrace_cleaner + + # reset the state + MissionControl::Jobs.backtrace_cleaner = nil + MissionControl::Jobs.applications = Applications.new + + # Setup the application with what we had before *minus* a backtrace cleaner + @applications.each do |application| + MissionControl::Jobs.applications.add(application.name).tap do |it| + application.servers.each do |server| + it.add_servers(server.name, server.queue_adapter) + end + end + end + end + + teardown do + # reset back to the known state before the start of the test + MissionControl::Jobs.backtrace_cleaner = @backtrace_cleaner + MissionControl::Jobs.applications = @application + end + + visit jobs_path(:failed) + within_job_row(/FailingJob\s*2/) do + click_on "RuntimeError: This always fails!" + end + + assert_no_selector ".backtrace-toggle-selector" + end + + test "click on 'clean' shows a backtrace cleaned by the Rails default backtrace cleaner" do + visit jobs_path(:failed) + within_job_row /FailingJob\s*2/ do + click_on "RuntimeError: This always fails!" + end + + assert_selector ".backtrace-toggle-selector" + + within ".backtrace-toggle-selector" do + click_on "Clean" + end + + assert_selector "pre.backtrace-content", text: /.*/, visible: true + + within ".backtrace-toggle-selector" do + click_on "Full" + end + + assert_selector "pre.backtrace-content", text: /.*/, visible: true + end end