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