diff --git a/app/jobs/rule_job.rb b/app/jobs/rule_job.rb index 92a23c086..c70c79fa9 100644 --- a/app/jobs/rule_job.rb +++ b/app/jobs/rule_job.rb @@ -40,7 +40,7 @@ class RuleJob < ApplicationJob status = "pending" elsif result.is_a?(Integer) # Only synchronous actions were executed - transactions_processed = result + transactions_processed = transactions_queued transactions_modified = result status = "success" else diff --git a/app/models/rule_run.rb b/app/models/rule_run.rb index 8f0ac759f..3028de44a 100644 --- a/app/models/rule_run.rb +++ b/app/models/rule_run.rb @@ -27,6 +27,10 @@ class RuleRun < ApplicationRecord status == "failed" end + def transactions_blocked + [ transactions_processed - transactions_modified, 0 ].max + end + # Thread-safe method to complete a job and update the run def complete_job!(modified_count: 0) with_lock do diff --git a/app/views/rules/index.html.erb b/app/views/rules/index.html.erb index 49282af42..00d0dfb94 100644 --- a/app/views/rules/index.html.erb +++ b/app/views/rules/index.html.erb @@ -128,6 +128,7 @@
<%= t("rules.recent_runs.columns.transactions_counts.queued") %>
<%= t("rules.recent_runs.columns.transactions_counts.processed") %>
<%= t("rules.recent_runs.columns.transactions_counts.modified") %>
+
<%= t("rules.recent_runs.columns.transactions_counts.blocked", default: "Blocked") %>
@@ -169,7 +170,7 @@ <%= run.rule_name.presence || run.rule&.name.presence || t("rules.recent_runs.unnamed_rule") %> - <%= "#{number_with_delimiter(run.transactions_queued)} / #{number_with_delimiter(run.transactions_processed)} / #{number_with_delimiter(run.transactions_modified)}" %> + <%= "#{number_with_delimiter(run.transactions_queued)} / #{number_with_delimiter(run.transactions_processed)} / #{number_with_delimiter(run.transactions_modified)} / #{number_with_delimiter(run.transactions_blocked)}" %> <% end %> diff --git a/config/locales/views/rules/en.yml b/config/locales/views/rules/en.yml index 81eccc7e8..5c6fac3bf 100644 --- a/config/locales/views/rules/en.yml +++ b/config/locales/views/rules/en.yml @@ -31,6 +31,7 @@ en: queued: Queued processed: Processed modified: Modified + blocked: Blocked execution_types: manual: Manual scheduled: Scheduled diff --git a/test/controllers/rules_controller_test.rb b/test/controllers/rules_controller_test.rb index baa30a1f3..cdbc49134 100644 --- a/test/controllers/rules_controller_test.rb +++ b/test/controllers/rules_controller_test.rb @@ -210,6 +210,26 @@ class RulesControllerTest < ActionDispatch::IntegrationTest end end + test "index shows blocked count in recent runs summary" do + rule = rules(:one) + RuleRun.create!( + rule: rule, + execution_type: "manual", + status: "success", + transactions_queued: 10, + transactions_processed: 7, + transactions_modified: 4, + pending_jobs_count: 0, + executed_at: Time.current + ) + + get rules_url + + assert_response :success + assert_select "th", text: /Queued\s+Processed\s+Modified\s+Blocked/ + assert_select "td", text: "10 / 7 / 4 / 3" + end + test "should get confirm_all" do get confirm_all_rules_url assert_response :success diff --git a/test/jobs/rule_job_test.rb b/test/jobs/rule_job_test.rb new file mode 100644 index 000000000..c7d1c462a --- /dev/null +++ b/test/jobs/rule_job_test.rb @@ -0,0 +1,54 @@ +require "test_helper" + +class RuleJobTest < ActiveJob::TestCase + include EntriesTestHelper + + setup do + @family = families(:empty) + @account = @family.accounts.create!(name: "Rule job test", balance: 1000, currency: "USD", accountable: Depository.new) + @food_and_dining = @family.categories.create!(name: "Food & Dining") + @groceries = @family.categories.create!(name: "Groceries") + end + + test "records manually locked matching transactions as blocked" do + 20.times do |index| + create_transaction( + account: @account, + name: "Whole Foods #{index}", + date: Date.current - index.days + ) + end + + manually_locked_transactions = @family.transactions + .joins(:entry) + .where("entries.name LIKE ?", "Whole Foods%") + .order("entries.date DESC") + .limit(10) + + manually_locked_transactions.each do |transaction| + transaction.update!(category: @groceries) + transaction.lock_attr!(:category_id) + transaction.entry.mark_user_modified! + end + + rule = @family.rules.create!( + name: "Whole Foods Testing", + resource_type: "transaction", + effective_date: 1.year.ago.to_date, + conditions: [ + Rule::Condition.new(condition_type: "transaction_name", operator: "like", value: "Whole Foods") + ], + actions: [ + Rule::Action.new(action_type: "set_transaction_category", value: @food_and_dining.id) + ] + ) + + RuleJob.perform_now(rule) + + rule_run = rule.rule_runs.order(:created_at).last + assert_equal 20, rule_run.transactions_queued + assert_equal 20, rule_run.transactions_processed + assert_equal 10, rule_run.transactions_modified + assert_equal 10, rule_run.transactions_blocked + end +end diff --git a/test/system/rules_test.rb b/test/system/rules_test.rb new file mode 100644 index 000000000..49f4601bc --- /dev/null +++ b/test/system/rules_test.rb @@ -0,0 +1,37 @@ +require "application_system_test_case" + +class RulesTest < ApplicationSystemTestCase + setup do + sign_in @user = users(:family_admin) + end + + test "shows queued processed modified and blocked counts for recent rule runs" do + rule = @user.family.rules.create!( + name: "Whole Foods Testing", + resource_type: "transaction", + effective_date: 1.year.ago.to_date, + conditions: [ + Rule::Condition.new(condition_type: "transaction_name", operator: "like", value: "Whole Foods") + ], + actions: [ + Rule::Action.new(action_type: "set_transaction_category", value: categories(:food_and_drink).id) + ] + ) + + rule.rule_runs.create!( + rule_name: rule.name, + execution_type: "manual", + status: "success", + transactions_queued: 20, + transactions_processed: 20, + transactions_modified: 10, + pending_jobs_count: 0, + executed_at: Time.current + ) + + visit rules_path + + assert_selector "th", text: /queued\s+processed\s+modified\s+blocked/i + assert_selector "td", text: "20 / 20 / 10 / 10" + end +end