Drop boosts

Killed by design.
This commit is contained in:
David Heinemeier Hansson
2025-04-12 18:52:54 +02:00
parent 1b732c41c7
commit 760cbb6c99
27 changed files with 23 additions and 299 deletions
-31
View File
@@ -471,37 +471,6 @@
}
}
.input.boost__input {
--hover-size: 0;
--input-border-radius: 0;
--input-border-size: 0;
--input-padding: 0;
--outline-size: 0;
color: inherit;
font-size: inherit;
font-weight: inherit;
inline-size: min-content;
max-inline-size: 3ch;
min-inline-size: 1ch;
outline: none;
@supports (field-sizing: content) {
field-sizing: content;
max-inline-size: unset;
}
&:focus {
background-color: var(--color-highlight);
}
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
.card__closed {
align-items: center;
aspect-ratio: 7/4;
@@ -1,12 +0,0 @@
class Cards::BoostsController < ApplicationController
include CardScoped
def create
count = if params[:boost_count].to_i == @card.boosts_count
@card.boosts_count + 1
else
params[:boost_count].to_i
end
@card.boost!(count)
end
end
+5 -9
View File
@@ -19,13 +19,9 @@ module EventsTimeline
end
def events_for_activity_day
user_events.where(created_at: @activity_day.all_day).
group_by { |event| [ event.created_at.hour, helpers.event_column(event) ] }.
map { |hour_col, events|
[ hour_col,
events.uniq { |e| e.action == "boosted" ? [ e.creator_id, e.card_id ] : e.id }
]
}
user_events.where(created_at: @activity_day.all_day)
.group_by { |event| [ event.created_at.hour, helpers.event_column(event) ] }
.map { |hour_col, events| [ hour_col, events.uniq { |e| e.id } ] }
end
def latest_event_before_activity_day
@@ -38,7 +34,7 @@ module EventsTimeline
def user_cards
Current.user.accessible_cards
.published_or_drafted_by(Current.user)
.where(collection_id: collection_filter)
.published_or_drafted_by(Current.user)
.where(collection_id: collection_filter)
end
end
-4
View File
@@ -72,8 +72,6 @@ module EventsHelper
end
when "unassigned"
"#{ event.creator == Current.user ? "You" : event.creator.name } unassigned #{ event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence } from <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
when "boosted"
"#{ event.creator == Current.user ? "You" : event.creator.name } boosted <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
when "commented"
"#{ event.creator == Current.user ? "You" : event.creator.name } commented on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
when "published"
@@ -99,8 +97,6 @@ module EventsHelper
case event.action
when "assigned"
"assigned"
when "boosted"
"thumb-up"
when "staged"
"bolt"
when "unstaged"
+1 -2
View File
@@ -1,5 +1,5 @@
class Card < ApplicationRecord
include Assignable, Boostable, Colored, DraftCommenting, Engageable, Eventable, Golden,
include Assignable, Colored, DraftCommenting, Engageable, Eventable, Golden,
Messages, Notifiable, Pinnable, Closeable, Scorable, Searchable, Staged,
Statuses, Taggable, Watchable
@@ -22,7 +22,6 @@ class Card < ApplicationRecord
scope :indexed_by, ->(index) do
case index
when "most_active" then ordered_by_activity
when "most_boosted" then ordered_by_boosts
when "newest" then reverse_chronologically
when "oldest" then chronologically
when "latest" then latest
-18
View File
@@ -1,18 +0,0 @@
module Card::Boostable
extend ActiveSupport::Concern
included do
scope :ordered_by_boosts, -> { order boosts_count: :desc }
end
def boost!(count = 1)
count = count.to_i
count = 1 if count < 1
transaction do
track_event :boosted, count: count
update! boosts_count: count
rescore
end
end
end
+1 -2
View File
@@ -36,7 +36,6 @@ module Card::Scorable
def event_weight(event)
case
when event.boosted? then 1
when event.comment&.first_by_author_on_card? then 20
when event.comment&.follows_comment_by_another_author? then 15
when event.commented? then 10
@@ -60,6 +59,6 @@ module Card::Scorable
end
def scorable_events
events.where(action: [ :commented, :boosted ])
events.where(action: :commented)
end
end
-6
View File
@@ -9,15 +9,9 @@ class Event < ApplicationRecord
has_one :comment, through: :message, source: :messageable, source_type: "Comment"
scope :chronologically, -> { order created_at: :asc, id: :desc }
scope :non_boosts, -> { where.not action: :boosted }
scope :boosts, -> { where action: :boosted }
after_create -> { card.touch_last_active_at }
def boosted?
action == "boosted"
end
def commented?
action == "commented"
end
+1 -13
View File
@@ -5,16 +5,12 @@ class EventSummary < ApplicationRecord
# FIXME: Consider persisting the body and compute at write time.
def body
"#{main_summary} #{boosts_summary}".squish
events.map { |event| summarize(event) }.join(" ")
end
private
delegate :time_ago_in_words, to: "ApplicationController.helpers"
def main_summary
events.non_boosts.map { |event| summarize(event) }.join(" ")
end
def summarize(event)
case event.action
when "published"
@@ -39,12 +35,4 @@ class EventSummary < ApplicationRecord
"#{event.creator.name} changed title from '#{event.particulars.dig('particulars', 'old_title')}' to '#{event.particulars.dig('particulars', 'new_title')}'."
end
end
def boosts_summary
if tally = events.boosts.group(:creator).count.presence
tally.map do |creator, count|
"#{creator.name} +#{count}"
end.to_sentence + "."
end
end
end
+1 -1
View File
@@ -1,7 +1,7 @@
module Filter::Fields
extend ActiveSupport::Concern
INDEXES = %w[ most_boosted newest oldest latest stalled ]
INDEXES = %w[ newest oldest latest stalled ]
delegate :default_value?, to: :class
-10
View File
@@ -1,10 +0,0 @@
<%= form_with url: card_boosts_path(card), class: "pad-inline flex align-center gap" do %>
<span class="overflow-ellipsis">
<strong><%= number_field_tag :boost_count, card.boosts_count, min: 1, class: "boost__input input", autocomplete: "off" %></strong>
<span><%= card.boosts_count == 1 ? "boost" : "boosts" %></span>
</span>
<%= tag.button class: "btn", type: :submit, style: "font-size: 0.4em" do %>
<%= icon_tag "add" %>
<span class="for-screen-reader">Boost</span>
<% end %>
<% end %>
@@ -1,7 +0,0 @@
<%= turbo_stream.update dom_id(@card, :boosts) do %>
<%= render "cards/boosts/boosts", card: @card %>
<% end %>
<%= turbo_stream.replace dom_id(@card, :messages) do %>
<%= render "cards/messages", card: @card %>
<% end %>
-1
View File
@@ -35,7 +35,6 @@ Rails.application.routes.draw do
resource :goldness
resources :assignments
resources :boosts
resources :stagings
resources :taggings
@@ -0,0 +1,5 @@
class RemoveBoostsCountFromCards < ActiveRecord::Migration[8.1]
def change
remove_column :cards, :boosts_count
end
end
Generated
+1 -3
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_04_12_084635) do
ActiveRecord::Schema[8.1].define(version: 2025_04_12_162339) do
create_table "accesses", force: :cascade do |t|
t.integer "collection_id", null: false
t.datetime "created_at", null: false
@@ -113,7 +113,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_12_084635) do
t.float "activity_score", default: 0.0, null: false
t.datetime "activity_score_at"
t.float "activity_score_order", default: 0.0, null: false
t.integer "boosts_count", default: 0, null: false
t.integer "collection_id", null: false
t.datetime "created_at", null: false
t.integer "creator_id", null: false
@@ -292,7 +291,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_12_084635) do
t.datetime "created_at", null: false
t.string "title"
t.datetime "updated_at", null: false
t.index ["account_id", "title"], name: "index_tags_on_account_id_and_title", unique: true
t.index ["account_id"], name: "index_tags_on_account_id"
end
+1 -28
View File
@@ -412,16 +412,6 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: boosts_count
cast_type: *1
sql_type_metadata: *2
'null': false
default: 0
default_function:
collation:
comment:
- *24
- *6
- &25 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
@@ -2005,23 +1995,6 @@ indexes:
comment:
valid: true
tags:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: tags
name: index_tags_on_account_id_and_title
unique: true
columns:
- account_id
- title
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: tags
name: index_tags_on_account_id
@@ -2154,4 +2127,4 @@ indexes:
nulls_not_distinct:
comment:
valid: true
version: 20250412084635
version: 20250412162339
@@ -1,27 +0,0 @@
require "test_helper"
class Cards::BoostsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "create" do
boost_count = cards(:logo).boosts_count
assert_difference "cards(:logo).reload.boosts_count", +1 do
post card_boosts_path(cards(:logo), params: { boost_count: boost_count }, format: :turbo_stream)
end
assert_turbo_stream action: :update, target: dom_id(cards(:logo), :boosts)
end
test "create with value" do
boost_count = 10
assert_changes "cards(:logo).reload.boosts_count", to: boost_count do
post card_boosts_path(cards(:logo), params: { boost_count: boost_count }, format: :turbo_stream)
end
assert_turbo_stream action: :update, target: dom_id(cards(:logo), :boosts)
end
end
-1
View File
@@ -4,7 +4,6 @@ logo:
title: The logo isn't big enough
due_on: <%= 3.days.from_now %>
created_at: <%= 1.week.ago %>
boosts_count: 5
activity_score: 7
status: published
last_active_at: <%= Time.current %>
-35
View File
@@ -13,13 +13,6 @@ logo_assignment_jz:
particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }.to_json %>
created_at: <%= 1.week.ago + 1.hour %>
logo_boost_dhh:
creator: david
card: logo
action: boosted
summary: logo_initial_activity
created_at: <%= 1.week.ago + 2.hours %>
logo_assignment_km:
creator: david
card: logo
@@ -28,34 +21,6 @@ logo_assignment_km:
particulars: <%= { assignee_ids: [ ActiveRecord::FixtureSet.identify(:kevin) ] }.to_json %>
created_at: <%= 1.day.ago %>
logo_boost_km1:
creator: kevin
card: logo
action: boosted
summary: logo_second_activity
created_at: <%= 1.day.ago + 1.hour %>
logo_boost_km2:
creator: kevin
card: logo
action: boosted
summary: logo_second_activity
created_at: <%= 1.day.ago + 2.hours %>
logo_boost_jz1:
creator: jz
card: logo
action: boosted
summary: logo_second_activity
created_at: <%= 1.day.ago + 3.hours %>
logo_boost_jz2:
creator: jz
card: logo
action: boosted
summary: logo_third_activity
created_at: <%= 1.hour.ago %>
layout_published:
creator: david
card: layout
+2 -2
View File
@@ -2,5 +2,5 @@ jz_assignments:
creator: david
tags: mobile
assignees: jz
fields: <%= { indexed_by: :most_boosted }.to_json %>
params_digest: <%= Filter.digest_params({ indexed_by: :most_boosted, tag_ids: [ ActiveRecord::FixtureSet.identify(:mobile) ], assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }) %>
fields: <%= { indexed_by: :newest }.to_json %>
params_digest: <%= Filter.digest_params({ indexed_by: :newest, tag_ids: [ ActiveRecord::FixtureSet.identify(:mobile) ], assignee_ids: [ ActiveRecord::FixtureSet.identify(:jz) ] }) %>
-7
View File
@@ -13,13 +13,6 @@ class CardMessagesTest < ActionDispatch::IntegrationTest
assert_predicate card.messages.last, :event_summary?
assert_equal "created", card.messages.last.messageable.events.sole.action
# Boost it
post card_boosts_path(card, format: :turbo_stream)
assert_equal 1, card.messages.count
assert_predicate card.messages.last, :event_summary?
assert_equal 2, card.messages.last.event_summary.events.count
assert_equal "boosted", card.messages.last.messageable.events.last.action
# Comment on it
post collection_card_comments_url(collections(:writebook), card), params: { comment: { body: "Agreed." } }
assert_equal 2, card.messages.count
-7
View File
@@ -1,7 +0,0 @@
require "test_helper"
class BoostTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
-46
View File
@@ -1,52 +1,6 @@
require "test_helper"
class Card::ScorableTest < ActiveSupport::TestCase
test "recent activity counts more than older activity in the ordering" do
with_current_user :kevin do
travel_to 5.days.ago
card_old = collections(:writebook).cards.create! status: :published, title: "old"
card_mid = collections(:writebook).cards.create! status: :published, title: "mid"
card_new = collections(:writebook).cards.create! status: :published, title: "new"
card_old.boost!
card_old.boost!
travel_back
travel_to 2.days.ago
card_mid.boost!
travel_back
card_new.boost!
assert_equal %w[ new mid old ], Card.where(id: [ card_old, card_mid, card_new ]).ordered_by_activity.map(&:title)
end
end
test "items with old activity are more stale than those with none, or with new activity" do
with_current_user :kevin do
travel_to 20.days.ago
card_old = collections(:writebook).cards.create! status: :published, title: "old"
card_new = collections(:writebook).cards.create! status: :published, title: "new"
card_none = collections(:writebook).cards.create! status: :published, title: "none"
card_old.boost!
card_old.boost!
travel_back
travel_to 2.days.ago
card_new.boost!
card_new.boost!
travel_back
assert_equal %w[ old new none ], Card.where(id: [ card_none, card_old, card_new ]).ordered_by_staleness.map(&:title)
card_old.boost!
assert_equal %w[ new old none ], Card.where(id: [ card_none, card_old, card_new ]).ordered_by_staleness.map(&:title)
end
end
test "cards with no activity have a valid activity_score_order" do
card = Card.create! collection: collections(:writebook), creator: users(:kevin)
-13
View File
@@ -13,14 +13,6 @@ class CardTest < ActiveSupport::TestCase
assert_equal "Agreed.", cards(:logo).messages.comments.last.messageable.body.to_plain_text.chomp
end
test "boosting" do
assert_changes -> { cards(:logo).activity_score } do
assert_difference -> { cards(:logo).boosts_count }, +1 do
cards(:logo).boost!(cards(:logo).boosts_count+ 1)
end
end
end
test "assignment states" do
assert cards(:logo).assigned_to?(users(:kevin))
assert_not cards(:logo).assigned_to?(users(:david))
@@ -74,11 +66,6 @@ class CardTest < ActiveSupport::TestCase
assert_includes Card.search("haggis"), card
end
test "ordering by boosts" do
cards(:layout).update! boosts_count: 1_000
assert_equal cards(:layout, :logo, :shipping, :text), Card.ordered_by_boosts
end
test "closed" do
assert_equal [ cards(:shipping) ], Card.closed
end
+2 -3
View File
@@ -2,9 +2,8 @@ require "test_helper"
class EventSummaryTest < ActiveSupport::TestCase
test "body" do
assert_equal "Added by David. Assigned to JZ. David +1.", event_summaries(:logo_initial_activity).body
assert_equal "Assigned to Kevin. Kevin +2 and JZ +1.", event_summaries(:logo_second_activity).body
assert_equal "JZ +1.", event_summaries(:logo_third_activity).body
assert_equal "Added by David. Assigned to JZ.", event_summaries(:logo_initial_activity).body
assert_equal "Assigned to Kevin.", event_summaries(:logo_second_activity).body
assert_equal "Added by Kevin.", event_summaries(:text_initial_activity).body
end
end
+3 -3
View File
@@ -101,13 +101,13 @@ class FilterTest < ActiveSupport::TestCase
end
test "summary" do
assert_equal "Most boosted, tagged #mobile, and assigned to JZ ", filters(:jz_assignments).summary
assert_equal "Newest, tagged #mobile, and assigned to JZ ", filters(:jz_assignments).summary
filters(:jz_assignments).update!(stages: workflow_stages(:qa_triage, :qa_in_progress))
assert_equal "Most boosted, tagged #mobile, assigned to JZ, and staged in Triage or In progress ", filters(:jz_assignments).summary
assert_equal "Newest, tagged #mobile, assigned to JZ, and staged in Triage or In progress ", filters(:jz_assignments).summary
filters(:jz_assignments).update!(stages: [], assignees: [], tags: [], collections: [ collections(:writebook) ])
assert_equal "Most boosted in Writebook", filters(:jz_assignments).summary
assert_equal "Newest in Writebook", filters(:jz_assignments).summary
filters(:jz_assignments).update!(indexed_by: "stalled")
assert_equal "Stalled in Writebook", filters(:jz_assignments).summary
-8
View File
@@ -5,14 +5,6 @@ class NotifierTest < ActiveSupport::TestCase
assert_kind_of Notifier::Published, Notifier.for(events(:logo_published))
end
test "for does not raise an error when the event is not notifiable" do
assert_nothing_raised do
assert_no_difference -> { Notification.count } do
Notifier.for(events(:logo_boost_dhh))
end
end
end
test "generate does not create notifications if the event was system-generated" do
cards(:logo).drafted!
events(:logo_published).update!(creator: accounts("37s").users.system)