diff --git a/app/javascript/controllers/card_size_controller.js b/app/javascript/controllers/card_size_controller.js deleted file mode 100644 index e688358da..000000000 --- a/app/javascript/controllers/card_size_controller.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -const SIZES = [ "one", "two", "three", "four", "five" ] - -export default class extends Controller { - static targets = [ "card" ] - - connect() { - this.resize() - } - - resize() { - const [ min, max ] = this.#getScoreRange() - - this.cardTargets.forEach(card => { - const score = this.#currentCardScore(card) - const idx = Math.round((score - min) / (max - min) * (SIZES.length - 1)) - - card.style.setProperty("--card-size", `var(--card-size-${SIZES[idx]})`) - }) - } - - #getScoreRange() { - var min = 0, max = 1; - - this.cardTargets.forEach(card => { - const score = this.#currentCardScore(card) - - min = Math.min(min, score) - max = Math.max(max, score) - }) - - return [ min, max ] - } - - #currentCardScore(el) { - const score = el.dataset.activityScore - const scoreAt = el.dataset.activityScoreAt - const daysAgo = (Date.now() / 1000 - scoreAt) / (60 * 60 * 24) - - return score / (2**daysAgo) - } -} diff --git a/app/models/card.rb b/app/models/card.rb index e2191aa41..4c17269c9 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -1,6 +1,6 @@ class Card < ApplicationRecord include Assignable, Colored, DraftCommenting, Engageable, Eventable, Golden, - Messages, Notifiable, Pinnable, Closeable, Scorable, Searchable, Staged, + Messages, Notifiable, Pinnable, Closeable, Searchable, Staged, Statuses, Taggable, Watchable belongs_to :collection, touch: true @@ -21,12 +21,11 @@ class Card < ApplicationRecord scope :indexed_by, ->(index) do case index - when "most_active" then ordered_by_activity - when "newest" then reverse_chronologically - when "oldest" then chronologically - when "latest" then latest - when "stalled" then ordered_by_staleness - when "closed" then closed + when "newest" then reverse_chronologically + when "oldest" then chronologically + when "latest" then latest + when "stalled" then chronologically + when "closed" then closed end end diff --git a/app/models/card/scorable.rb b/app/models/card/scorable.rb deleted file mode 100644 index 91b0856b5..000000000 --- a/app/models/card/scorable.rb +++ /dev/null @@ -1,64 +0,0 @@ -module Card::Scorable - extend ActiveSupport::Concern - - REFERENCE_DATE = Time.utc(2025, 1, 1) - - included do - scope :ordered_by_activity, -> { order activity_score_order: :desc } - - # Staleness is measured by the amount of activity_score lost since it was last updated. - # The factor 0.9 is chosen to make the decay curve closer to linear; an exponential - # curve would make recent items appear stale very quickly, due to the sharp dropoff. - scope :ordered_by_staleness, -> { order Arel.sql( - "coalesce(activity_score * (1 - power(0.9, julianday('now') - julianday(activity_score_at))), 0) desc" - ) } - end - - def rescore - score = calculate_activity_score - score_at = last_scorable_activity_at - - update! \ - activity_score: score, - activity_score_at: score_at, - activity_score_order: event_score_reference(score, score_at) - end - - private - def calculate_activity_score - scorable_events.sum { |event| event_score(event) } - end - - def event_score(event) - days_ago = (last_scorable_activity_at - event.created_at) / 1.day - event_weight(event) / (2**days_ago) - end - - def event_weight(event) - case - 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 - else 0 - end - end - - def event_score_reference(score, activity_at) - # The reference score is used to make the activity score comparable - # across different cards, since it represents the card's activity - # level at a consistent point in time. - # - # We store this as log2 to tame the huge/tiny numbers we'd otherwise get - # when activity is far from the reference date. - days_diff = (activity_at - REFERENCE_DATE) / 1.day - Math.log2(1.0 + score) + days_diff - end - - def last_scorable_activity_at - scorable_events.maximum(:created_at) || created_at - end - - def scorable_events - events.where(action: :commented) - end -end diff --git a/app/views/cards/index/engagement/_doing.html.erb b/app/views/cards/index/engagement/_doing.html.erb index e59c70700..216866e85 100644 --- a/app/views/cards/index/engagement/_doing.html.erb +++ b/app/views/cards/index/engagement/_doing.html.erb @@ -1,4 +1,4 @@ -
+
<% if workflow = filter.single_workflow %> <%= render "cards/index/workflow_filter", workflow: workflow, filter: filter %> <% else %> diff --git a/db/migrate/20250418150259_remove_activity_from_cards.rb b/db/migrate/20250418150259_remove_activity_from_cards.rb new file mode 100644 index 000000000..f8910ea9b --- /dev/null +++ b/db/migrate/20250418150259_remove_activity_from_cards.rb @@ -0,0 +1,8 @@ +class RemoveActivityFromCards < ActiveRecord::Migration[8.1] + def change + remove_index :cards, :activity_score_order + remove_column :cards, :activity_score_order + remove_column :cards, :activity_score_at + remove_column :cards, :activity_score + end +end diff --git a/db/schema.rb b/db/schema.rb index 06a8eb245..3a56a34ae 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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_15_131804) do +ActiveRecord::Schema[8.1].define(version: 2025_04_18_150259) do create_table "accesses", force: :cascade do |t| t.integer "collection_id", null: false t.datetime "created_at", null: false @@ -108,9 +108,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_15_131804) do end create_table "cards", force: :cascade do |t| - 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 "collection_id", null: false t.datetime "created_at", null: false t.integer "creator_id", null: false @@ -120,7 +117,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_15_131804) do t.text "status", default: "creating", null: false t.string "title" t.datetime "updated_at", null: false - t.index ["activity_score_order"], name: "index_cards_on_activity_score_order" t.index ["collection_id"], name: "index_cards_on_collection_id" t.index ["last_active_at", "status"], name: "index_cards_on_last_active_at_and_status" t.index ["stage_id"], name: "index_cards_on_stage_id" diff --git a/db/schema_cache.yml b/db/schema_cache.yml index f662880fe..8152c393d 100644 --- a/db/schema_cache.yml +++ b/db/schema_cache.yml @@ -21,7 +21,7 @@ columns: default_function: collation: comment: - - &24 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &22 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: collection_id cast_type: *1 @@ -80,7 +80,7 @@ columns: default_function: collation: comment: - - &30 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &28 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: user_id cast_type: *1 @@ -374,47 +374,9 @@ columns: - *9 cards: - *5 - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: activity_score - cast_type: &22 !ruby/object:ActiveModel::Type::Float - precision: - scale: - limit: - sql_type_metadata: &23 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata - sql_type: float - type: :float - limit: - precision: - scale: - 'null': false - default: 0.0 - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: activity_score_at - cast_type: *3 - sql_type_metadata: *4 - 'null': true - default: - default_function: - collation: - comment: - - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column - auto_increment: - name: activity_score_order - cast_type: *22 - sql_type_metadata: *23 - 'null': false - default: 0.0 - default_function: - collation: - comment: - - *24 + - *22 - *6 - - &25 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &23 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: creator_id cast_type: *1 @@ -427,12 +389,12 @@ columns: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: due_on - cast_type: &26 !ruby/object:ActiveRecord::Type::Date + cast_type: &24 !ruby/object:ActiveRecord::Type::Date precision: scale: limit: timezone: - sql_type_metadata: &27 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type_metadata: &25 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata sql_type: date type: :date limit: @@ -473,7 +435,7 @@ columns: default_function: collation: comment: - - &32 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &30 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: title cast_type: *7 @@ -528,11 +490,11 @@ columns: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: all_access - cast_type: &33 !ruby/object:ActiveModel::Type::Boolean + cast_type: &31 !ruby/object:ActiveModel::Type::Boolean precision: scale: limit: - sql_type_metadata: &34 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type_metadata: &32 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata sql_type: boolean type: :boolean limit: @@ -544,7 +506,7 @@ columns: collation: comment: - *6 - - *25 + - *23 - *10 - *9 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column @@ -558,15 +520,15 @@ columns: collation: comment: collections_filters: - - *24 + - *22 - *18 comments: - *5 - *6 - - *25 + - *23 - *9 creators_filters: - - *25 + - *23 - *18 event_summaries: - *5 @@ -586,12 +548,12 @@ columns: comment: - *21 - *6 - - *25 + - *23 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: due_date - cast_type: *26 - sql_type_metadata: *27 + cast_type: *24 + sql_type_metadata: *25 'null': true default: default_function: @@ -600,11 +562,11 @@ columns: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: particulars - cast_type: &28 !ruby/object:ActiveRecord::Type::Json + cast_type: &26 !ruby/object:ActiveRecord::Type::Json precision: scale: limit: - sql_type_metadata: &29 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type_metadata: &27 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata sql_type: json type: :json limit: @@ -629,12 +591,12 @@ columns: filters: - *5 - *6 - - *25 + - *23 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: fields - cast_type: *28 - sql_type_metadata: *29 + cast_type: *26 + sql_type_metadata: *27 'null': false default: "{}" default_function: @@ -665,7 +627,7 @@ columns: comment: filters_tags: - *18 - - &31 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &29 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: tag_id cast_type: *1 @@ -745,13 +707,13 @@ columns: collation: comment: - *9 - - *30 + - *28 pins: - *5 - *21 - *6 - *9 - - *30 + - *28 reactions: - *5 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column @@ -831,25 +793,25 @@ columns: default_function: collation: comment: - - *30 + - *28 taggings: - *5 - *21 - *6 - - *31 + - *29 - *9 tags: - *5 - *6 - - *32 + - *30 - *9 users: - *5 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: active - cast_type: *33 - sql_type_metadata: *34 + cast_type: *31 + sql_type_metadata: *32 'null': false default: true default_function: @@ -893,12 +855,12 @@ columns: - *21 - *6 - *9 - - *30 + - *28 - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: watching - cast_type: *33 - sql_type_metadata: *34 + cast_type: *31 + sql_type_metadata: *32 'null': false default: true default_function: @@ -1350,22 +1312,6 @@ indexes: nulls_not_distinct: comment: valid: true - - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition - table: cards - name: index_cards_on_activity_score_order - unique: false - columns: - - activity_score_order - lengths: {} - orders: {} - opclasses: {} - where: - type: - using: - include: - nulls_not_distinct: - comment: - valid: true closure_reasons: [] closures: - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition @@ -1992,4 +1938,4 @@ indexes: comment: valid: true workflows: [] -version: 20250415131804 +version: 20250418150259 diff --git a/test/fixtures/cards.yml b/test/fixtures/cards.yml index f49179eb3..c8dc163a3 100644 --- a/test/fixtures/cards.yml +++ b/test/fixtures/cards.yml @@ -4,7 +4,6 @@ logo: title: The logo isn't big enough due_on: <%= 3.days.from_now %> created_at: <%= 1.week.ago %> - activity_score: 7 status: published last_active_at: <%= Time.current %> @@ -13,7 +12,6 @@ layout: creator: david title: Layout is broken created_at: <%= 1.week.ago %> - activity_score: 1 status: published last_active_at: <%= Time.current %> diff --git a/test/models/card/scorable_test.rb b/test/models/card/scorable_test.rb deleted file mode 100644 index f24785e63..000000000 --- a/test/models/card/scorable_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require "test_helper" - -class Card::ScorableTest < ActiveSupport::TestCase - test "cards with no activity have a valid activity_score_order" do - card = Card.create! collection: collections(:writebook), creator: users(:kevin) - - card.rescore - - assert card.activity_score.zero? - assert_not card.activity_score_order.infinite? - end -end