From dbbee2c6614d3e86448e6b11b7bebab199c9367c Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Tue, 18 Nov 2025 11:08:27 -0500 Subject: [PATCH] Stalled cards are unstalled by anything that touches updated_at ref: https://app.fizzy.do/5986089/cards/2492 --- app/models/card/entropic.rb | 7 ++++--- app/models/card/stallable.rb | 6 ++++-- test/models/card/stallable_test.rb | 33 +++++++++++++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/app/models/card/entropic.rb b/app/models/card/entropic.rb index 4b11a2099..23e33c18a 100644 --- a/app/models/card/entropic.rb +++ b/app/models/card/entropic.rb @@ -7,16 +7,17 @@ module Card::Entropic .joins(board: :account) .left_outer_joins(board: :entropy) .joins("LEFT OUTER JOIN entropies AS account_entropies ON account_entropies.account_id = accounts.id AND account_entropies.container_type = 'Account' AND account_entropies.container_id = accounts.id") - .where("last_active_at <= DATE_SUB(NOW(), INTERVAL COALESCE(entropies.auto_postpone_period, account_entropies.auto_postpone_period) SECOND)") + .where("last_active_at <= DATE_SUB(?, INTERVAL COALESCE(entropies.auto_postpone_period, account_entropies.auto_postpone_period) SECOND)", Time.now) end scope :postponing_soon, -> do + now = Time.now active .joins(board: :account) .left_outer_joins(board: :entropy) .joins("LEFT OUTER JOIN entropies AS account_entropies ON account_entropies.account_id = accounts.id AND account_entropies.container_type = 'Account' AND account_entropies.container_id = accounts.id") - .where("last_active_at > DATE_SUB(NOW(), INTERVAL COALESCE(entropies.auto_postpone_period, account_entropies.auto_postpone_period) SECOND)") - .where("last_active_at <= DATE_SUB(NOW(), INTERVAL CAST(COALESCE(entropies.auto_postpone_period, account_entropies.auto_postpone_period) * 0.75 AS SIGNED) SECOND)") + .where("last_active_at > DATE_SUB(?, INTERVAL COALESCE(entropies.auto_postpone_period, account_entropies.auto_postpone_period) SECOND)", now) + .where("last_active_at <= DATE_SUB(?, INTERVAL CAST(COALESCE(entropies.auto_postpone_period, account_entropies.auto_postpone_period) * 0.75 AS SIGNED) SECOND)", now) end delegate :auto_postpone_period, to: :board diff --git a/app/models/card/stallable.rb b/app/models/card/stallable.rb index bc935b5f6..545cc87ee 100644 --- a/app/models/card/stallable.rb +++ b/app/models/card/stallable.rb @@ -7,14 +7,16 @@ module Card::Stallable has_one :activity_spike, class_name: "Card::ActivitySpike", dependent: :destroy scope :with_activity_spikes, -> { joins(:activity_spike) } - scope :stalled, -> { open.active.with_activity_spikes.where("card_activity_spikes.updated_at": ..STALLED_AFTER_LAST_SPIKE_PERIOD.ago) } + scope :stalled, -> { open.active.with_activity_spikes.where("card_activity_spikes.updated_at": ..STALLED_AFTER_LAST_SPIKE_PERIOD.ago, updated_at: ..STALLED_AFTER_LAST_SPIKE_PERIOD.ago) } before_update :remember_to_detect_activity_spikes after_update_commit :detect_activity_spikes_later, if: :should_detect_activity_spikes? end def stalled? - open? && last_activity_spike_at < STALLED_AFTER_LAST_SPIKE_PERIOD.ago if activity_spike.present? + if activity_spike.present? + open? && last_activity_spike_at < STALLED_AFTER_LAST_SPIKE_PERIOD.ago && updated_at < STALLED_AFTER_LAST_SPIKE_PERIOD.ago + end end def last_activity_spike_at diff --git a/test/models/card/stallable_test.rb b/test/models/card/stallable_test.rb index 35995e7ee..0f5b3dbf4 100644 --- a/test/models/card/stallable_test.rb +++ b/test/models/card/stallable_test.rb @@ -20,7 +20,29 @@ class Card::StallableTest < ActiveSupport::TestCase end test "a card with an old activity spike is stalled" do - cards(:logo).create_activity_spike!(updated_at: 3.months.ago) + cards(:logo).create_activity_spike! + + travel_to 3.months.from_now + + assert cards(:logo).stalled? + assert_includes Card.stalled, cards(:logo) + end + + test "a stalled card can be unstalled with a single comment" do + cards(:logo).create_activity_spike! + + travel_to 3.months.from_now + + assert cards(:logo).stalled? + assert_includes Card.stalled, cards(:logo) + + cards(:logo).comments.create!(body: "A new comment to unstall the card") + + assert_not cards(:logo).stalled? + assert_not_includes Card.stalled, cards(:logo) + + # and stalls again after more time passes + travel_to 3.months.from_now assert cards(:logo).stalled? assert_includes Card.stalled, cards(:logo) @@ -28,15 +50,20 @@ class Card::StallableTest < ActiveSupport::TestCase test "a card with an old activity spike is not stalled after being postponed" do card = cards(:logo) - card.update!(last_active_at: 1.day.ago - card.board.entropy.auto_postpone_period) - card.create_activity_spike!(updated_at: 3.months.ago) + card.create_activity_spike! + + travel_to 3.months.from_now assert card.stalled? assert_includes Card.stalled, card + travel_to Time.now + card.board.entropy.auto_postpone_period + 1.day + assert_includes Card.due_to_be_postponed, card + Card.auto_postpone_all_due assert_not card.reload.stalled? + assert_not_includes Card.stalled, card end # More fine-grained testing in Card::ActivitySpike::Detector