Merge pull request #508 from basecamp/flavorjones/entropy-concern

refactor: Extract a Card::Entropy concern
This commit is contained in:
Mike Dalessio
2025-06-01 16:55:14 -04:00
committed by GitHub
10 changed files with 117 additions and 96 deletions
+2 -2
View File
@@ -52,12 +52,12 @@
}
}
.bubble--closing {
.bubble--considering {
inset-inline-start: calc(-1 * var(--bubble-gap));
translate: -100% -50%;
}
.bubble--considering {
.bubble--doing {
inset-inline-end: calc(-1 * var(--bubble-gap));
translate: 100% -50%;
}
+3 -3
View File
@@ -1,7 +1,7 @@
class Card < ApplicationRecord
include Assignable, Colored, Engageable, Eventable, Golden,
Mentions, Pinnable, Closeable, Readable, Searchable, Staged,
Statuses, Taggable, Watchable
include Assignable, Colored, Engageable, Entropy, Eventable,
Golden, Mentions, Pinnable, Closeable, Readable, Searchable,
Staged, Statuses, Taggable, Watchable
belongs_to :collection, touch: true
belongs_to :creator, class_name: "User", default: -> { Current.user }
-26
View File
@@ -1,8 +1,6 @@
module Card::Closeable
extend ActiveSupport::Concern
AUTO_CLOSE_REMINDER_BEFORE = 7.days
included do
has_one :closure, dependent: :destroy
@@ -10,30 +8,6 @@ module Card::Closeable
scope :open, -> { where.missing(:closure) }
scope :recently_closed_first, -> { closed.order("closures.created_at": :desc) }
scope :in_auto_closing_collection, -> { joins(:collection).merge(Collection.auto_closing) }
scope :due_to_be_closed, -> { considering.in_auto_closing_collection.where("last_active_at <= DATETIME('now', '-' || auto_close_period || ' seconds')") }
delegate :auto_closing?, :auto_close_period, to: :collection
end
class_methods do
def auto_close_all_due
due_to_be_closed.find_each do |card|
card.close(user: User.system, reason: "Closed")
end
end
end
def auto_close_at
last_active_at + auto_close_period if auto_closing? && last_active_at
end
def days_until_close
(auto_close_at.to_date - Date.current).to_i if auto_close_at
end
def closing_soon?
considering? && auto_closing? && Time.current >= auto_close_at - AUTO_CLOSE_REMINDER_BEFORE
end
def closed?
+8 -14
View File
@@ -1,15 +1,11 @@
module Card::Engageable
extend ActiveSupport::Concern
STAGNATED_AFTER = 30.days
AUTO_RECONSIDER_REMINDER_BEFORE = 7.days
included do
has_one :engagement, dependent: :destroy, class_name: "Card::Engagement"
scope :considering, -> { published_or_drafted_by(Current.user).open.where.missing(:engagement) }
scope :doing, -> { published.open.joins(:engagement) }
scope :stagnated, -> { doing.where(last_active_at: ..STAGNATED_AFTER.ago) }
scope :by_engagement_status, ->(status) do
case status.to_s
@@ -19,16 +15,6 @@ module Card::Engageable
end
end
class_methods do
def auto_reconsider_all_stagnated
stagnated.find_each(&:reconsider)
end
end
def auto_reconsider_at
last_active_at + STAGNATED_AFTER if last_active_at
end
def doing?
open? && published? && engagement.present?
end
@@ -37,6 +23,14 @@ module Card::Engageable
open? && published? && engagement.blank?
end
def engagement_status
if doing?
"doing"
elsif considering?
"considering"
end
end
def engage
unless doing?
transaction do
+47
View File
@@ -0,0 +1,47 @@
module Card::Entropy
extend ActiveSupport::Concern
AUTO_RECONSIDER_PERIOD = 30.days
ENTROPY_REMINDER_BEFORE = 7.days
included do
scope :in_auto_closing_collection, -> { joins(:collection).merge(Collection.auto_closing) }
scope :stagnated, -> { doing.where(last_active_at: ..AUTO_RECONSIDER_PERIOD.ago) }
scope :due_to_be_closed, -> { considering.in_auto_closing_collection.where("last_active_at <= DATETIME('now', '-' || auto_close_period || ' seconds')") }
delegate :auto_closing?, :auto_close_period, to: :collection
end
class_methods do
def auto_close_all_due
due_to_be_closed.find_each do |card|
card.close(user: User.system, reason: "Closed")
end
end
def auto_reconsider_all_stagnated
stagnated.find_each(&:reconsider)
end
end
def subject_to_entropy?
doing? || (auto_closing? && considering?)
end
def auto_close_at
last_active_at + auto_close_period if auto_closing? && last_active_at
end
def days_until_close
(auto_close_at.to_date - Date.current).to_i if auto_close_at
end
def auto_reconsider_at
last_active_at + AUTO_RECONSIDER_PERIOD if last_active_at
end
def days_until_reconsider
(auto_reconsider_at.to_date - Date.current).to_i if auto_reconsider_at
end
end
+2 -6
View File
@@ -33,12 +33,8 @@
<%= render "cards/display/common/background", card: card %>
<% if card.auto_closing? %>
<% if card.considering? %>
<%= render "cards/display/preview/bubble", card: card, style: "closing" %>
<% elsif card.doing? %>
<%= render "cards/display/preview/bubble", card: card, style: "considering" %>
<% end %>
<% if card.subject_to_entropy? %>
<%= render "cards/display/preview/bubble", card: card %>
<% end %>
<% end %>
<% end %>
@@ -1,6 +1,6 @@
<div hidden class="bubble bubble--<%= style %>"
<div hidden class="bubble bubble--<%= card.engagement_status %>"
data-controller="bubble" data-action="turbo:morph-element->bubble#update"
data-bubble-reminder-before-value="<%= Card::AUTO_CLOSE_REMINDER_BEFORE.in_days.to_i %>"
data-bubble-reminder-before-value="<%= Card::ENTROPY_REMINDER_BEFORE.in_days.to_i %>"
data-bubble-closes-at-value="<%= card.auto_close_at.iso8601 %>"
data-bubble-entropy-action-value="<%= card_entropy_action(card) %>">
<svg viewBox="0 0 200 100">
-26
View File
@@ -26,30 +26,4 @@ class Card::CloseableTest < ActiveSupport::TestCase
cards(:logo).update! last_active_at: 2.day.ago
assert_equal (123-2).days.from_now, cards(:logo).auto_close_at
end
test "auto close all due" do
cards(:logo, :shipping).each(&:reconsider)
cards(:logo).update!(last_active_at: 1.day.ago - collections(:writebook).auto_close_period)
cards(:shipping).update!(last_active_at: 1.day.from_now - collections(:writebook).auto_close_period)
assert_difference -> { Card.closed.count }, +1 do
Card.auto_close_all_due
end
assert cards(:logo).reload.closed?
assert_not cards(:shipping).reload.closed?
end
test "don't auto close those cards where the collection has no auto close period" do
cards(:logo, :shipping).each(&:reconsider)
collections(:writebook).update auto_close_period: nil
assert_no_difference -> { Card.closed.count } do
Card.auto_close_all_due
end
assert_not cards(:logo).reload.closed?
end
end
+3 -17
View File
@@ -11,6 +11,9 @@ class Card::EngageableTest < ActiveSupport::TestCase
assert_not cards(:logo).considering?
assert cards(:text).considering?
assert_equal "doing", cards(:logo).engagement_status
assert_equal "considering", cards(:text).engagement_status
end
test "change the engagement" do
@@ -46,21 +49,4 @@ class Card::EngageableTest < ActiveSupport::TestCase
assert_includes Card.considering, cards(:text)
assert_not_includes Card.considering, cards(:logo)
end
test "auto_reconsider_all_stagnated" do
travel_to Time.current
cards(:logo, :shipping).each(&:engage)
cards(:logo).update!(last_active_at: 1.day.ago - Card::Engageable::STAGNATED_AFTER)
cards(:shipping).update!(last_active_at: 1.day.from_now - Card::Engageable::STAGNATED_AFTER)
assert_difference -> { Card.considering.count }, +1 do
Card.auto_reconsider_all_stagnated
end
assert cards(:shipping).reload.doing?
assert cards(:logo).reload.considering?
assert_equal Time.current, cards(:logo).last_active_at
end
end
+50
View File
@@ -0,0 +1,50 @@
require "test_helper"
class Card::EntropyTest < ActiveSupport::TestCase
setup do
Current.session = sessions(:david)
end
test "auto close all due" do
cards(:logo, :shipping).each(&:reconsider)
cards(:logo).update!(last_active_at: 1.day.ago - collections(:writebook).auto_close_period)
cards(:shipping).update!(last_active_at: 1.day.from_now - collections(:writebook).auto_close_period)
assert_difference -> { Card.closed.count }, +1 do
Card.auto_close_all_due
end
assert cards(:logo).reload.closed?
assert_not cards(:shipping).reload.closed?
end
test "don't auto close those cards where the collection has no auto close period" do
cards(:logo, :shipping).each(&:reconsider)
collections(:writebook).update auto_close_period: nil
assert_no_difference -> { Card.closed.count } do
Card.auto_close_all_due
end
assert_not cards(:logo).reload.closed?
end
test "auto_reconsider_all_stagnated" do
travel_to Time.current
cards(:logo, :shipping).each(&:engage)
cards(:logo).update!(last_active_at: 1.day.ago - Card::AUTO_RECONSIDER_PERIOD)
cards(:shipping).update!(last_active_at: 1.day.from_now - Card::AUTO_RECONSIDER_PERIOD)
assert_difference -> { Card.considering.count }, +1 do
Card.auto_reconsider_all_stagnated
end
assert cards(:shipping).reload.doing?
assert cards(:logo).reload.considering?
assert_equal Time.current, cards(:logo).last_active_at
end
end